Browse Source

feat: add proxy targets support in site and stream #904

Jacky 1 month ago
parent
commit
7580fa4572
43 changed files with 2490 additions and 669 deletions
  1. 23 24
      api/backup/auto_backup.go
  2. 19 8
      api/sites/list.go
  3. 27 12
      api/streams/streams.go
  4. 149 14
      api/upstream/upstream.go
  5. 2 0
      app/components.d.ts
  6. 7 0
      app/src/api/site.ts
  7. 3 1
      app/src/api/stream.ts
  8. 109 0
      app/src/components/ProxyTargets/ProxyTargets.vue
  9. 3 0
      app/src/components/ProxyTargets/index.ts
  10. 55 35
      app/src/language/ar/app.po
  11. 55 35
      app/src/language/de_DE/app.po
  12. 48 32
      app/src/language/en/app.po
  13. 55 35
      app/src/language/es/app.po
  14. 55 32
      app/src/language/fr_FR/app.po
  15. 55 32
      app/src/language/ja_JP/app.po
  16. 55 35
      app/src/language/ko_KR/app.po
  17. 55 38
      app/src/language/messages.pot
  18. 55 35
      app/src/language/pt_PT/app.po
  19. 55 35
      app/src/language/ru_RU/app.po
  20. 55 36
      app/src/language/tr_TR/app.po
  21. 55 32
      app/src/language/uk_UA/app.po
  22. 55 35
      app/src/language/vi_VN/app.po
  23. 55 35
      app/src/language/zh_CN/app.po
  24. 55 35
      app/src/language/zh_TW/app.po
  25. 2 0
      app/src/pinia/index.ts
  26. 142 0
      app/src/pinia/moudule/proxyAvailability.ts
  27. 15 2
      app/src/views/site/site_list/columns.tsx
  28. 0 36
      app/src/views/stream/StreamList.vue
  29. 65 24
      app/src/views/stream/columns.tsx
  30. 13 10
      app/src/views/stream/components/RightPanel/Basic.vue
  31. 4 3
      app/src/views/stream/components/StreamEditor.vue
  32. 103 0
      app/src/views/stream/components/StreamStatusSelect.vue
  33. 4 3
      app/src/views/stream/store.ts
  34. 7 7
      internal/cache/index.go
  35. 5 0
      internal/config/config.go
  36. 57 0
      internal/helper/debouncer.go
  37. 101 0
      internal/helper/debouncer_test.go
  38. 14 8
      internal/site/index.go
  39. 5 0
      internal/site/type.go
  40. 58 0
      internal/stream/index.go
  41. 79 0
      internal/stream/index_test.go
  42. 275 0
      internal/upstream/proxy_parser.go
  43. 381 0
      internal/upstream/proxy_parser_test.go

+ 23 - 24
api/backup/auto_backup.go

@@ -35,7 +35,7 @@ func GetAutoBackupList(c *gin.Context) {
 // Request Body: AutoBackup model with required fields
 // Response: Created auto backup configuration
 func CreateAutoBackup(c *gin.Context) {
-	ctx := cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
+	cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
 		"name":                 "required",
 		"backup_type":          "required",
 		"storage_type":         "required",
@@ -54,16 +54,15 @@ func CreateAutoBackup(c *gin.Context) {
 			ctx.AbortWithError(err)
 			return
 		}
-	})
-
-	ctx.Create()
-
-	// Register cron job only if the backup is enabled
-	if ctx.Model.Enabled {
-		if err := cron.AddAutoBackupJob(ctx.Model.ID, ctx.Model.CronExpression); err != nil {
-			logger.Errorf("Failed to add auto backup job %d: %v", ctx.Model.ID, err)
+	}).ExecutedHook(func(ctx *cosy.Ctx[model.AutoBackup]) {
+		// Register cron job only if the backup is enabled
+		if ctx.Model.Enabled {
+			if err := cron.AddAutoBackupJob(ctx.Model.ID, ctx.Model.CronExpression); err != nil {
+				ctx.AbortWithError(err)
+				return
+			}
 		}
-	}
+	}).Create()
 }
 
 // GetAutoBackup retrieves a single auto backup configuration by ID.
@@ -85,7 +84,7 @@ func GetAutoBackup(c *gin.Context) {
 // Request Body: Partial AutoBackup model with fields to update
 // Response: Updated auto backup configuration
 func ModifyAutoBackup(c *gin.Context) {
-	ctx := cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
+	cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
 		"name":                 "omitempty",
 		"backup_type":          "omitempty",
 		"storage_type":         "omitempty",
@@ -104,20 +103,20 @@ func ModifyAutoBackup(c *gin.Context) {
 			ctx.AbortWithError(err)
 			return
 		}
-	})
-
-	ctx.Modify()
-
-	// Update cron job based on enabled status
-	if ctx.Model.Enabled {
-		if err := cron.UpdateAutoBackupJob(ctx.Model.ID, ctx.Model.CronExpression); err != nil {
-			logger.Errorf("Failed to update auto backup job %d: %v", ctx.Model.ID, err)
+	}).ExecutedHook(func(ctx *cosy.Ctx[model.AutoBackup]) {
+		// Update cron job based on enabled status
+		if ctx.Model.Enabled {
+			if err := cron.UpdateAutoBackupJob(ctx.Model.ID, ctx.Model.CronExpression); err != nil {
+				ctx.AbortWithError(err)
+				return
+			}
+		} else {
+			if err := cron.RemoveAutoBackupJob(ctx.Model.ID); err != nil {
+				ctx.AbortWithError(err)
+				return
+			}
 		}
-	} else {
-		if err := cron.RemoveAutoBackupJob(ctx.Model.ID); err != nil {
-			logger.Errorf("Failed to remove auto backup job %d: %v", ctx.Model.ID, err)
-		}
-	}
+	}).Modify()
 }
 
 // DestroyAutoBackup deletes an auto backup configuration and removes its cron job.

+ 19 - 8
api/sites/list.go

@@ -103,15 +103,26 @@ func GetSiteList(c *gin.Context) {
 
 		indexedSite := site.GetIndexedSite(file.Name())
 
+		// Convert site.ProxyTarget to config.ProxyTarget
+		var proxyTargets []config.ProxyTarget
+		for _, target := range indexedSite.ProxyTargets {
+			proxyTargets = append(proxyTargets, config.ProxyTarget{
+				Host: target.Host,
+				Port: target.Port,
+				Type: target.Type,
+			})
+		}
+
 		configs = append(configs, config.Config{
-			Name:       file.Name(),
-			ModifiedAt: fileInfo.ModTime(),
-			Size:       fileInfo.Size(),
-			IsDir:      fileInfo.IsDir(),
-			Status:     configStatusMap[file.Name()],
-			EnvGroupID: envGroupId,
-			EnvGroup:   envGroup,
-			Urls:       indexedSite.Urls,
+			Name:         file.Name(),
+			ModifiedAt:   fileInfo.ModTime(),
+			Size:         fileInfo.Size(),
+			IsDir:        fileInfo.IsDir(),
+			Status:       configStatusMap[file.Name()],
+			EnvGroupID:   envGroupId,
+			EnvGroup:     envGroup,
+			Urls:         indexedSite.Urls,
+			ProxyTargets: proxyTargets,
 		})
 	}
 

+ 27 - 12
api/streams/streams.go

@@ -23,7 +23,7 @@ import (
 type Stream struct {
 	ModifiedAt      time.Time                      `json:"modified_at"`
 	Advanced        bool                           `json:"advanced"`
-	Enabled         bool                           `json:"enabled"`
+	Status          config.ConfigStatus            `json:"status"`
 	Name            string                         `json:"name"`
 	Config          string                         `json:"config"`
 	ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
@@ -32,6 +32,7 @@ type Stream struct {
 	EnvGroupID      uint64                         `json:"env_group_id"`
 	EnvGroup        *model.EnvGroup                `json:"env_group,omitempty"`
 	SyncNodeIDs     []uint64                       `json:"sync_node_ids" gorm:"serializer:json"`
+	ProxyTargets    []config.ProxyTarget           `json:"proxy_targets,omitempty"`
 }
 
 func GetStreams(c *gin.Context) {
@@ -130,15 +131,29 @@ func GetStreams(c *gin.Context) {
 			continue
 		}
 
+		// Get indexed stream for proxy targets
+		indexedStream := stream.GetIndexedStream(file.Name())
+
+		// Convert stream.ProxyTarget to config.ProxyTarget
+		var proxyTargets []config.ProxyTarget
+		for _, target := range indexedStream.ProxyTargets {
+			proxyTargets = append(proxyTargets, config.ProxyTarget{
+				Host: target.Host,
+				Port: target.Port,
+				Type: target.Type,
+			})
+		}
+
 		// Add the config to the result list after passing all filters
 		configs = append(configs, config.Config{
-			Name:       file.Name(),
-			ModifiedAt: fileInfo.ModTime(),
-			Size:       fileInfo.Size(),
-			IsDir:      fileInfo.IsDir(),
-			Status:     enabledConfigMap[file.Name()],
-			EnvGroupID: envGroupId,
-			EnvGroup:   envGroup,
+			Name:         file.Name(),
+			ModifiedAt:   fileInfo.ModTime(),
+			Size:         fileInfo.Size(),
+			IsDir:        fileInfo.IsDir(),
+			Status:       enabledConfigMap[file.Name()],
+			EnvGroupID:   envGroupId,
+			EnvGroup:     envGroup,
+			ProxyTargets: proxyTargets,
 		})
 	}
 
@@ -164,9 +179,9 @@ func GetStream(c *gin.Context) {
 	}
 
 	// Check if the stream is enabled
-	enabled := true
+	status := config.StatusEnabled
 	if _, err := os.Stat(nginx.GetConfPath("streams-enabled", name)); os.IsNotExist(err) {
-		enabled = false
+		status = config.StatusDisabled
 	}
 
 	// Retrieve or create ChatGPT log for this stream
@@ -201,7 +216,7 @@ func GetStream(c *gin.Context) {
 		c.JSON(http.StatusOK, Stream{
 			ModifiedAt:      file.ModTime(),
 			Advanced:        streamModel.Advanced,
-			Enabled:         enabled,
+			Status:          status,
 			Name:            name,
 			Config:          string(origContent),
 			ChatGPTMessages: chatgpt.Content,
@@ -223,7 +238,7 @@ func GetStream(c *gin.Context) {
 	c.JSON(http.StatusOK, Stream{
 		ModifiedAt:      file.ModTime(),
 		Advanced:        streamModel.Advanced,
-		Enabled:         enabled,
+		Status:          status,
 		Name:            name,
 		Config:          nginxConfig.FmtCode(),
 		Tokenized:       nginxConfig,

+ 149 - 14
api/upstream/upstream.go

@@ -1,15 +1,23 @@
 package upstream
 
 import (
+	"context"
+	"net/http"
+	"sync"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"time"
 )
 
+type wsMessage struct {
+	data interface{}
+	done chan error
+}
+
 func AvailabilityTest(c *gin.Context) {
 	var upGrader = websocket.Upgrader{
 		CheckOrigin: func(r *http.Request) bool {
@@ -25,24 +33,151 @@ func AvailabilityTest(c *gin.Context) {
 
 	defer ws.Close()
 
-	var body []string
+	var currentTargets []string
+	var targetsMutex sync.RWMutex
 
-	err = ws.ReadJSON(&body)
+	// Use context to manage goroutine lifecycle
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
 
-	if err != nil {
-		logger.Error(err)
-		return
+	// Use channel to serialize WebSocket write operations, avoiding concurrent conflicts
+	writeChan := make(chan wsMessage, 10)
+	testChan := make(chan bool, 1) // Immediate test signal
+
+	// Create debouncer for test execution
+	testDebouncer := helper.NewDebouncer(300 * time.Millisecond)
+
+	// WebSocket writer goroutine - serialize all write operations
+	go func() {
+		defer logger.Debug("WebSocket writer goroutine stopped")
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			case msg := <-writeChan:
+				err := ws.WriteJSON(msg.data)
+				if msg.done != nil {
+					msg.done <- err
+					close(msg.done)
+				}
+				if err != nil {
+					logger.Error("Failed to send WebSocket message:", err)
+					if helper.IsUnexpectedWebsocketError(err) {
+						cancel() // Cancel all goroutines
+					}
+				}
+			}
+		}
+	}()
+
+	// Safe WebSocket write function
+	writeJSON := func(data interface{}) error {
+		done := make(chan error, 1)
+		msg := wsMessage{data: data, done: done}
+
+		select {
+		case writeChan <- msg:
+			return <-done
+		case <-ctx.Done():
+			return ctx.Err()
+		case <-time.After(5 * time.Second): // Prevent write blocking
+			return context.DeadlineExceeded
+		}
 	}
 
-	for {
-		err = ws.WriteJSON(upstream.AvailabilityTest(body))
-		if err != nil {
-			if helper.IsUnexpectedWebsocketError(err) {
-				logger.Error(err)
+	// Function to perform availability test
+	performTest := func() {
+		targetsMutex.RLock()
+		targets := make([]string, len(currentTargets))
+		copy(targets, currentTargets)
+		targetsMutex.RUnlock()
+
+		logger.Debug("Performing availability test for targets:", targets)
+
+		if len(targets) > 0 {
+			logger.Debug("Starting upstream.AvailabilityTest...")
+			result := upstream.AvailabilityTest(targets)
+			logger.Debug("Test completed, results:", result)
+
+			logger.Debug("Sending results via WebSocket...")
+			if err := writeJSON(result); err != nil {
+				logger.Error("Failed to send WebSocket message:", err)
+				if helper.IsUnexpectedWebsocketError(err) {
+					cancel() // Cancel all goroutines
+				}
+			} else {
+				logger.Debug("Results sent successfully")
+			}
+		} else {
+			logger.Debug("No targets to test")
+			// Send empty result even if no targets
+			emptyResult := make(map[string]interface{})
+			if err := writeJSON(emptyResult); err != nil {
+				logger.Error("Failed to send empty result:", err)
+			} else {
+				logger.Debug("Empty result sent successfully")
+			}
+		}
+	}
+
+	// Goroutine to handle incoming messages (target updates)
+	go func() {
+		defer logger.Debug("WebSocket reader goroutine stopped")
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			default:
+			}
+
+			var newTargets []string
+			// Set read timeout to avoid blocking
+			ws.SetReadDeadline(time.Now().Add(30 * time.Second))
+			err := ws.ReadJSON(&newTargets)
+			ws.SetReadDeadline(time.Time{}) // Clear deadline
+
+			if err != nil {
+				if helper.IsUnexpectedWebsocketError(err) {
+					logger.Error(err)
+				}
+				cancel() // Cancel all goroutines
+				return
 			}
-			break
+
+			logger.Debug("Received targets from frontend:", newTargets)
+
+			targetsMutex.Lock()
+			currentTargets = newTargets
+			targetsMutex.Unlock()
+
+			// Use debouncer to trigger test execution
+			testDebouncer.Trigger(func() {
+				select {
+				case testChan <- true:
+				default:
+				}
+			})
 		}
+	}()
 
-		time.Sleep(10 * time.Second)
+	// Main testing loop
+	ticker := time.NewTicker(10 * time.Second)
+	defer ticker.Stop()
+
+	logger.Debug("WebSocket connection established, waiting for messages...")
+
+	for {
+		select {
+		case <-ctx.Done():
+			testDebouncer.Stop()
+			logger.Debug("WebSocket connection closed")
+			return
+		case <-testChan:
+			// Debounce triggered test or first test
+			go performTest() // Execute asynchronously to avoid blocking main loop
+		case <-ticker.C:
+			// Periodic test execution
+			go performTest() // Execute asynchronously to avoid blocking main loop
+		}
 	}
 }

+ 2 - 0
app/components.d.ts

@@ -100,6 +100,8 @@ declare module 'vue' {
     PortScannerPortScanner: typeof import('./src/components/PortScanner/PortScanner.vue')['default']
     PortScannerPortScannerCompact: typeof import('./src/components/PortScanner/PortScannerCompact.vue')['default']
     ProcessingStatusProcessingStatus: typeof import('./src/components/ProcessingStatus/ProcessingStatus.vue')['default']
+    ProxyTargets: typeof import('./src/components/ProxyTargets.vue')['default']
+    ProxyTargetsProxyTargets: typeof import('./src/components/ProxyTargets/ProxyTargets.vue')['default']
     ReactiveFromNowReactiveFromNow: typeof import('./src/components/ReactiveFromNow/ReactiveFromNow.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']

+ 7 - 0
app/src/api/site.ts

@@ -8,6 +8,12 @@ import { extendCurdApi, http, useCurdApi } from '@uozi-admin/request'
 
 export type SiteStatus = ConfigStatus.Enabled | ConfigStatus.Disabled | ConfigStatus.Maintenance
 
+export interface ProxyTarget {
+  host: string
+  port: string
+  type: string // "proxy_pass" or "upstream"
+}
+
 export interface Site extends ModelBase {
   modified_at: string
   path: string
@@ -23,6 +29,7 @@ export interface Site extends ModelBase {
   env_group?: EnvGroup
   sync_node_ids: number[]
   urls?: string[]
+  proxy_targets?: ProxyTarget[]
   status: SiteStatus
 }
 

+ 3 - 1
app/src/api/stream.ts

@@ -1,12 +1,13 @@
 import type { EnvGroup } from './env_group'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
+import type { ProxyTarget, SiteStatus } from '@/api/site'
 import { extendCurdApi, http, useCurdApi } from '@uozi-admin/request'
 
 export interface Stream {
   modified_at: string
   advanced: boolean
-  enabled: boolean
+  status: SiteStatus
   name: string
   filepath: string
   config: string
@@ -15,6 +16,7 @@ export interface Stream {
   env_group_id: number
   env_group?: EnvGroup
   sync_node_ids: number[]
+  proxy_targets?: ProxyTarget[]
 }
 
 const baseUrl = '/streams'

+ 109 - 0
app/src/components/ProxyTargets/ProxyTargets.vue

@@ -0,0 +1,109 @@
+<script setup lang="ts">
+import type { ProxyTarget } from '@/api/site'
+import { useProxyAvailabilityStore } from '@/pinia/moudule/proxyAvailability'
+
+interface Props {
+  targets: ProxyTarget[]
+}
+
+const props = defineProps<Props>()
+
+const proxyStore = useProxyAvailabilityStore()
+let componentId = ''
+
+// Register component and watch for target changes
+watch(() => props.targets, newTargets => {
+  componentId = proxyStore.registerComponent(newTargets)
+}, {
+  immediate: true,
+  deep: true,
+})
+
+// Cleanup when component unmounts
+onBeforeUnmount(() => {
+  proxyStore.unregisterComponent(componentId)
+})
+
+function getTargetColor(target: ProxyTarget): string {
+  const result = proxyStore.getAvailabilityResult(target)
+  if (!result)
+    return 'default'
+  return result.online ? 'green' : 'red'
+}
+
+function getTargetText(target: ProxyTarget): string {
+  const result = proxyStore.getAvailabilityResult(target)
+  if (!result)
+    return `${target.host}:${target.port}`
+
+  if (result.online) {
+    return `${target.host}:${target.port} (${result.latency.toFixed(2)}ms)`
+  }
+  else {
+    return `${target.host}:${target.port} (${$gettext('Offline')})`
+  }
+}
+
+function getTargetTitle(target: ProxyTarget): string {
+  return `${$gettext('Type')}: ${target.type === 'upstream' ? $gettext('Upstream') : $gettext('Proxy Pass')}`
+}
+</script>
+
+<template>
+  <div v-if="targets.length > 0" class="proxy-targets">
+    <ATag
+      v-for="target in targets"
+      :key="proxyStore.getTargetKey(target)"
+      :color="getTargetColor(target)"
+      class="proxy-target-tag"
+      :bordered="false"
+    >
+      <template #icon>
+        <ATooltip
+          :title="getTargetTitle(target)"
+          placement="bottom"
+          class="cursor-pointer"
+        >
+          <span v-if="target.type === 'upstream'" class="target-type-icon">U</span>
+          <span v-else class="target-type-icon">P</span>
+        </ATooltip>
+      </template>
+      {{ getTargetText(target) }}
+    </ATag>
+  </div>
+</template>
+
+<style scoped lang="less">
+.proxy-targets {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+  max-width: 100%;
+  overflow: hidden;
+}
+
+.proxy-target-tag {
+  margin-right: 4px;
+  margin-bottom: 4px;
+  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
+  font-size: 12px;
+  max-width: 100%;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+
+  .target-type-icon {
+    display: inline-block;
+    width: 12px;
+    height: 12px;
+    line-height: 12px;
+    text-align: center;
+    background: rgba(255, 255, 255, 0.2);
+    border-radius: 2px;
+    margin-right: 4px;
+    font-weight: bold;
+    font-size: 10px;
+    flex-shrink: 0;
+  }
+}
+</style>

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

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

+ 55 - 35
app/src/language/ar/app.po

@@ -143,7 +143,7 @@ msgstr "إجراء"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "الإجراءات"
@@ -161,7 +161,7 @@ msgstr "النسبة الفعلية للعامل إلى المُهيأ"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "إضافة"
 
@@ -188,11 +188,11 @@ msgstr "أضف مكان"
 msgid "Add Site"
 msgstr "أضف موقع"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "أضف Stream"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "تمت الإضافة بنجاح"
 
@@ -276,7 +276,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "هل أنت متأكد أنك تريد الحذف نهائيًا؟"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "هل أنت متأكد أنك تريد الحذف؟"
 
@@ -567,6 +567,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "إلغاء"
 
@@ -1203,7 +1204,7 @@ msgstr "حدد اسم منطقة الذاكرة المشتركة والحجم،
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "حذف"
 
@@ -1251,7 +1252,7 @@ msgstr "فشل حذف الدفق %{name} من %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "تم حذف الدفق %{name} من %{node} بنجاح"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "حذف البث: ‎%{stream_name}"
 
@@ -1325,13 +1326,10 @@ msgid "Directory path to store cache files"
 msgstr "مسار الدليل لتخزين ملفات الذاكرة المؤقتة"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "تعطيل"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "تعطيل"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "فشل تعطيل التجديد التلقائي لـ %{name}"
@@ -1392,15 +1390,16 @@ msgstr "تم تعطيل الدفق %{name} من %{node} بنجاح"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "معطل"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "تم التعطيل بنجاح"
 
@@ -1430,6 +1429,10 @@ msgstr "لا تقم بتمكين هذا الخيار إلا إذا كنت متأ
 msgid "Do you want to %{action} this site?"
 msgstr "هل تريد %{action} هذا الموقع؟"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "هل تريد %{action} هذا البث؟"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "هل تريد تعطيل التجديد التلقائي للشهادة؟"
@@ -1505,7 +1508,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "مكرر"
 
@@ -1554,13 +1557,10 @@ msgid "Email (*)"
 msgstr "البريد الإلكتروني (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "تمكين"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "تفعيل"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "تم تفعيل المصادقة الثنائية بنجاح"
@@ -1659,9 +1659,10 @@ msgstr "تفعيل TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "مفعل"
@@ -1669,7 +1670,7 @@ msgstr "مفعل"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "تم التفعيل بنجاح"
 
@@ -1906,7 +1907,7 @@ msgstr "فشل حذف الشهادة من قاعدة البيانات: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "فشل في تعطيل %{msg}"
 
@@ -1916,7 +1917,7 @@ msgstr "فشل تعطيل وضع الصيانة %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "فشل في التفعيل %{msg}"
 
@@ -2655,7 +2656,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "صيانة"
 
@@ -2684,7 +2685,7 @@ msgstr "إدارة التكوينات"
 msgid "Manage Sites"
 msgstr "إدارة المواقع"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "إدارة التدفقات"
 
@@ -2858,12 +2859,12 @@ msgstr "توجيه متعدد الأسطر"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "اسم"
 
@@ -3124,7 +3125,7 @@ msgstr "يتضمن Nginx.conf دليل streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "لا"
 
@@ -3146,7 +3147,7 @@ msgid "Node"
 msgstr "العقدة"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "مجموعة العقد"
@@ -3259,6 +3260,7 @@ msgstr "إيقاف"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3276,7 +3278,8 @@ msgstr "غير متصل"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "حسنًا"
 
@@ -3667,6 +3670,15 @@ msgstr "مزود"
 msgid "Proxy"
 msgstr "وكيل"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "تمرير الوكيل"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "أهداف الوكيل"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "رقم الأمن العام"
@@ -4527,7 +4539,7 @@ msgstr "ثابت"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "الحالة"
 
@@ -5099,6 +5111,7 @@ msgstr "الثلاثاء"
 msgid "Two-factor authentication required"
 msgstr "يتطلب المصادقة الثنائية"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5135,7 +5148,7 @@ msgstr "تم التحديث بنجاح"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5169,6 +5182,10 @@ msgstr "تحميل الملفات"
 msgid "Upload Folders"
 msgstr "تحميل المجلدات"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "أعلى التيار"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "اسم المنبع"
@@ -5454,6 +5471,12 @@ msgstr "رموزك القديمة لن تعمل بعد الآن."
 msgid "Your passkeys"
 msgstr "مفاتيح المرور الخاصة بك"
 
+#~ msgid "Disable"
+#~ msgstr "تعطيل"
+
+#~ msgid "Enable"
+#~ msgstr "تفعيل"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "خطأ آخر نسخة احتياطية"
 
@@ -5653,9 +5676,6 @@ msgstr "مفاتيح المرور الخاصة بك"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "فشل مزامنة التكوين %{config_name} إلى %{env_name}، الاستجابة: %{resp}"
 
-#~ msgid "Target"
-#~ msgstr "الهدف"
-
 #~ msgid ""
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "2FA."

+ 55 - 35
app/src/language/de_DE/app.po

@@ -143,7 +143,7 @@ msgstr "Aktion"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Aktionen"
@@ -161,7 +161,7 @@ msgstr "Tatsächliches Verhältnis von Arbeitern zu konfigurierten"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Hinzufügen"
 
@@ -188,11 +188,11 @@ msgstr "Ort hinzufügen"
 msgid "Add Site"
 msgstr "Seite hinzufügen"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Stream hinzufügen"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Erfolgreich hinzugefügt"
 
@@ -278,7 +278,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Sind Sie sicher, dass Sie dauerhaft löschen möchten?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Sind Sie sicher, dass Sie löschen möchten?"
 
@@ -579,6 +579,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Abbrechen"
 
@@ -1218,7 +1219,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Löschen"
 
@@ -1266,7 +1267,7 @@ msgstr "Löschen des Streams %{name} von %{node} fehlgeschlagen"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Stream %{name} wurde erfolgreich von %{node} gelöscht"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Stream löschen: %{stream_name}"
 
@@ -1340,13 +1341,10 @@ msgid "Directory path to store cache files"
 msgstr "Verzeichnispfad zum Speichern der Cache-Dateien"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Deaktivieren"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Deaktivieren"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Automatische Verlängerung für %{name} konnte nicht deaktiviert werden"
@@ -1407,15 +1405,16 @@ msgstr "Stream %{name} von %{node} erfolgreich deaktiviert"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Deaktiviert"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Erfolgreich deaktiviert"
 
@@ -1447,6 +1446,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgstr "Möchten Sie diese Website %{action}?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Möchten Sie diesen Stream %{action}?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Möchtest du die automatische Zertifikatsverlängerung deaktivieren?"
@@ -1525,7 +1528,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Duplizieren"
 
@@ -1574,13 +1577,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "aktivieren"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Aktivieren"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA erfolgreich aktiviert"
@@ -1679,9 +1679,10 @@ msgstr "TOTP aktivieren"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Aktiviert"
@@ -1689,7 +1690,7 @@ msgstr "Aktiviert"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Erfolgreich aktiviert"
 
@@ -1926,7 +1927,7 @@ msgstr "Löschen des Zertifikats aus der Datenbank fehlgeschlagen: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Deaktivierung von %{msg} fehlgeschlagen"
 
@@ -1936,7 +1937,7 @@ msgstr "Deaktivierung des Wartungsmodus fehlgeschlagen: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Aktiviern von %{msg} fehlgeschlagen"
 
@@ -2682,7 +2683,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Wartung"
 
@@ -2712,7 +2713,7 @@ msgstr "Verwalte Konfigurationen"
 msgid "Manage Sites"
 msgstr "Verwalte Seiten"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Streams verwalten"
 
@@ -2886,12 +2887,12 @@ msgstr "Mehrzeilige Direktive"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Name"
 
@@ -3154,7 +3155,7 @@ msgstr "Nginx.conf enthält das streams-enabled-Verzeichnis"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Nein"
 
@@ -3176,7 +3177,7 @@ msgid "Node"
 msgstr "Node"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Node-Gruppe"
@@ -3292,6 +3293,7 @@ msgstr "Aus"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3309,7 +3311,8 @@ msgstr "Offline"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "OK"
 
@@ -3715,6 +3718,15 @@ msgstr "Anbieter"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Proxy-Weiterleitung"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Proxy-Ziele"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Öffentliche Sicherheitsnummer"
@@ -4589,7 +4601,7 @@ msgstr "Statisch"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Status"
 
@@ -5174,6 +5186,7 @@ msgstr "Dienstag"
 msgid "Two-factor authentication required"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5210,7 +5223,7 @@ msgstr "Erfolgreich aktualisiert"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5244,6 +5257,10 @@ msgstr "Dateien hochladen"
 msgid "Upload Folders"
 msgstr "Ordner hochladen"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Upstream"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Upstream-Name"
@@ -5543,6 +5560,12 @@ msgstr "Ihre alten Codes funktionieren nicht mehr."
 msgid "Your passkeys"
 msgstr "Deine Passkeys"
 
+#~ msgid "Disable"
+#~ msgstr "Deaktivieren"
+
+#~ msgid "Enable"
+#~ msgstr "Aktivieren"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Letzter Sicherungsfehler"
 
@@ -5749,9 +5772,6 @@ msgstr "Deine Passkeys"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "Speichern erfolgreich"
 
-#~ msgid "Target"
-#~ msgstr "Ziel"
-
 #~ msgid "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 #~ msgstr "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 

+ 48 - 32
app/src/language/en/app.po

@@ -127,7 +127,7 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr ""
@@ -145,7 +145,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr ""
 
@@ -172,11 +172,11 @@ msgstr ""
 msgid "Add Site"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr ""
 
@@ -260,7 +260,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr ""
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr ""
 
@@ -549,6 +549,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr ""
 
@@ -1144,7 +1145,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr ""
 
@@ -1192,7 +1193,7 @@ msgstr ""
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 
@@ -1266,13 +1267,10 @@ msgid "Directory path to store cache files"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr ""
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr ""
@@ -1333,15 +1331,16 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr ""
 
@@ -1371,6 +1370,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgstr ""
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr ""
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr ""
@@ -1445,7 +1448,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr ""
 
@@ -1494,13 +1497,10 @@ msgid "Email (*)"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr ""
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr ""
@@ -1599,9 +1599,10 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr ""
@@ -1609,7 +1610,7 @@ msgstr ""
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr ""
 
@@ -1846,7 +1847,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr ""
 
@@ -1856,7 +1857,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr ""
 
@@ -2576,7 +2577,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr ""
 
@@ -2603,7 +2604,7 @@ msgstr ""
 msgid "Manage Sites"
 msgstr ""
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr ""
 
@@ -2777,12 +2778,12 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr ""
 
@@ -3043,7 +3044,7 @@ msgstr ""
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr ""
 
@@ -3065,7 +3066,7 @@ msgid "Node"
 msgstr ""
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr ""
@@ -3174,6 +3175,7 @@ msgstr ""
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3191,7 +3193,8 @@ msgstr ""
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr ""
 
@@ -3576,6 +3579,14 @@ msgstr ""
 msgid "Proxy"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr ""
+
+#: src/views/site/site_list/columns.tsx:64 src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr ""
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr ""
@@ -4424,7 +4435,7 @@ msgstr ""
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr ""
 
@@ -4947,6 +4958,7 @@ msgstr ""
 msgid "Two-factor authentication required"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -4983,7 +4995,7 @@ msgstr ""
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5017,6 +5029,10 @@ msgstr ""
 msgid "Upload Folders"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr ""

+ 55 - 35
app/src/language/es/app.po

@@ -150,7 +150,7 @@ msgstr "Acción"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Acciones"
@@ -168,7 +168,7 @@ msgstr "Proporción real de trabajadores a configurados"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Agregar"
 
@@ -195,11 +195,11 @@ msgstr "Agregar Ubicación"
 msgid "Add Site"
 msgstr "Agregar Sitio"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Agregar Stream"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Agregado exitoso"
 
@@ -285,7 +285,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "¿Estás seguro de que quieres eliminar permanentemente?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "¿Está seguro de que quiere borrar?"
 
@@ -588,6 +588,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Cancelar"
 
@@ -1229,7 +1230,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Eliminar"
 
@@ -1277,7 +1278,7 @@ msgstr "Error al eliminar el flujo %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Se eliminó el flujo %{name} de %{node} correctamente"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar stream: %{site_name}"
 
@@ -1351,13 +1352,10 @@ msgid "Directory path to store cache files"
 msgstr "Ruta del directorio para almacenar archivos de caché"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Desactivar"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Desactivar"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Error al desactivar la renovación automática para %{name}"
@@ -1418,15 +1416,16 @@ msgstr "Deshabilitar el flujo %{name} desde %{node} con éxito"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Desactivado"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Desactivado con éxito"
 
@@ -1456,6 +1455,10 @@ msgstr "No habilite esta opción a menos que esté seguro de que la necesita."
 msgid "Do you want to %{action} this site?"
 msgstr "¿Desea %{action} este sitio?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "¿Quieres %{action} este flujo?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "¿Desea deshabilitar la renovación automática de certificado?"
@@ -1534,7 +1537,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Duplicar"
 
@@ -1583,13 +1586,10 @@ msgid "Email (*)"
 msgstr "Correo (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "habilitar"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Habilitar"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "Habilitar 2FA exitoso"
@@ -1688,9 +1688,10 @@ msgstr "Habilitar TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Habilitado"
@@ -1698,7 +1699,7 @@ msgstr "Habilitado"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Habilitado con éxito"
 
@@ -1937,7 +1938,7 @@ msgstr "Error al eliminar el certificado de la base de datos: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Error al deshabilitar %{msg}"
 
@@ -1947,7 +1948,7 @@ msgstr "Error al desactivar el modo de mantenimiento: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Error al habilitar %{msg}"
 
@@ -2691,7 +2692,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Mantenimiento"
 
@@ -2720,7 +2721,7 @@ msgstr "Administrar configuraciones"
 msgid "Manage Sites"
 msgstr "Administrar sitios"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Administrar Transmisiones"
 
@@ -2894,12 +2895,12 @@ msgstr "Directiva multilínea"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Nombre"
 
@@ -3162,7 +3163,7 @@ msgstr "Nginx.conf incluye el directorio streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "No"
 
@@ -3184,7 +3185,7 @@ msgid "Node"
 msgstr "Nodo"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Grupo de nodos"
@@ -3300,6 +3301,7 @@ msgstr "Apagado"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3317,7 +3319,8 @@ msgstr "Desconectado"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "OK"
 
@@ -3726,6 +3729,15 @@ msgstr "Proveedor"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Pase de Proxy"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Objetivos del proxy"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Número de Seguridad Pública"
@@ -4598,7 +4610,7 @@ msgstr "Estático"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Estado"
 
@@ -5182,6 +5194,7 @@ msgstr "Martes"
 msgid "Two-factor authentication required"
 msgstr "Se requiere autenticación de dos factores"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5218,7 +5231,7 @@ msgstr "Actualización exitosa"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5252,6 +5265,10 @@ msgstr "Subir archivos"
 msgid "Upload Folders"
 msgstr "Subir carpetas"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Aguas arriba"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Nombre de la Transmisión"
@@ -5546,6 +5563,12 @@ msgstr "Tus códigos antiguos ya no funcionarán."
 msgid "Your passkeys"
 msgstr "Sus llaves de acceso"
 
+#~ msgid "Disable"
+#~ msgstr "Desactivar"
+
+#~ msgid "Enable"
+#~ msgstr "Habilitar"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Último error de copia de seguridad"
 
@@ -5743,9 +5766,6 @@ msgstr "Sus llaves de acceso"
 #~ "Sincronización de la configuración %{config_name} a %{env_name} fallida, "
 #~ "respuesta: %{resp}"
 
-#~ msgid "Target"
-#~ msgstr "Objetivo"
-
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "¿No puede escanear? Utilice la vinculación con una llave de texto"
 

+ 55 - 32
app/src/language/fr_FR/app.po

@@ -148,7 +148,7 @@ msgstr "Action"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Actions"
@@ -166,7 +166,7 @@ msgstr "Ratio réel des travailleurs par rapport à la configuration"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Ajouter"
 
@@ -193,11 +193,11 @@ msgstr "Ajouter une localisation"
 msgid "Add Site"
 msgstr "Ajouter un site"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Ajouter un flux"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Ajouté avec succès"
 
@@ -283,7 +283,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Êtes-vous sûr de vouloir supprimer définitivement ?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 
@@ -583,6 +583,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Annuler"
 
@@ -1225,7 +1226,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Supprimer"
 
@@ -1273,7 +1274,7 @@ msgstr "Échec de la suppression du flux %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Le flux %{name} a été supprimé de %{node} avec succès"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Supprimer le flux : %{stream_name}"
 
@@ -1347,13 +1348,10 @@ msgid "Directory path to store cache files"
 msgstr "Chemin du répertoire pour stocker les fichiers de cache"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Désactiver"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Désactiver"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Échec de la désactivation du renouvellement automatique pour %{name}"
@@ -1414,15 +1412,16 @@ msgstr "Désactivation du flux %{name} depuis %{node} réussie"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Désactivé"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Désactivé avec succès"
 
@@ -1452,6 +1451,10 @@ msgstr "N'activez pas cette option sauf si vous êtes sûr d'en avoir avez besoi
 msgid "Do you want to %{action} this site?"
 msgstr "Voulez-vous %{action} ce site ?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Voulez-vous %{action} ce flux ?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Voulez-vous désactiver le renouvellement automatique des certificats ?"
@@ -1528,7 +1531,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Dupliquer"
 
@@ -1577,13 +1580,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "activer"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Activer"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA activé avec succès"
@@ -1682,9 +1682,10 @@ msgstr "Activer TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Activé"
@@ -1692,7 +1693,7 @@ msgstr "Activé"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Activé avec succès"
 
@@ -1929,7 +1930,7 @@ msgstr "Échec de la suppression du certificat de la base de données : %{error}
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Impossible de désactiver %{msg}"
 
@@ -1939,7 +1940,7 @@ msgstr "Échec de la désactivation du mode maintenance : %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Impossible d'activer %{msg}"
 
@@ -2689,7 +2690,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Maintenance"
 
@@ -2718,7 +2719,7 @@ msgstr "Gérer les configurations"
 msgid "Manage Sites"
 msgstr "Gérer les sites"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Gérer les flux"
 
@@ -2892,12 +2893,12 @@ msgstr "Directive multiligne"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Nom"
 
@@ -3160,7 +3161,7 @@ msgstr "Nginx.conf inclut le répertoire streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Non"
 
@@ -3182,7 +3183,7 @@ msgid "Node"
 msgstr "Nœud"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Groupe de nœuds"
@@ -3297,6 +3298,7 @@ msgstr "Désactivé"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3314,7 +3316,8 @@ msgstr "Hors ligne"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "OK"
 
@@ -3723,6 +3726,15 @@ msgstr "Fournisseur"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Passe de Proxy"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Cibles du proxy"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Numéro de sécurité publique"
@@ -4597,7 +4609,7 @@ msgstr "Statique"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Statut"
 
@@ -5190,6 +5202,7 @@ msgstr "Mardi"
 msgid "Two-factor authentication required"
 msgstr "Authentification à deux facteurs requise"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5226,7 +5239,7 @@ msgstr "Mise à jour réussie"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5260,6 +5273,10 @@ msgstr "Téléverser des fichiers"
 msgid "Upload Folders"
 msgstr "Télécharger des dossiers"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Amont"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Nom de l'amont"
@@ -5557,6 +5574,12 @@ msgstr "Vos anciens codes ne fonctionneront plus."
 msgid "Your passkeys"
 msgstr "Vos clés d'accès"
 
+#~ msgid "Disable"
+#~ msgstr "Désactiver"
+
+#~ msgid "Enable"
+#~ msgstr "Activer"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Dernière erreur de sauvegarde"
 

+ 55 - 32
app/src/language/ja_JP/app.po

@@ -144,7 +144,7 @@ msgstr "操作"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "操作"
@@ -162,7 +162,7 @@ msgstr "実際のワーカー数と設定値の比率"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "追加"
 
@@ -189,11 +189,11 @@ msgstr "Locationを追加"
 msgid "Add Site"
 msgstr "サイトを追加"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Streamを追加"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "正常に追加されました"
 
@@ -277,7 +277,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "完全に削除してもよろしいですか?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "削除してもよろしいですか?"
 
@@ -568,6 +568,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "キャンセル"
 
@@ -1183,7 +1184,7 @@ msgstr "共有メモリゾーンの名前とサイズを定義します(例: p
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "削除"
 
@@ -1231,7 +1232,7 @@ msgstr "%{node} からのストリーム %{name} の削除に失敗しました"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "ストリーム %{name} を %{node} から削除しました"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "ストリームを削除: %{stream_name}"
 
@@ -1305,13 +1306,10 @@ msgid "Directory path to store cache files"
 msgstr "キャッシュファイルを保存するディレクトリパス"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "無効化"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "無効化"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name} の自動更新の無効化に失敗しました"
@@ -1372,15 +1370,16 @@ msgstr "ストリーム %{name} を %{node} から無効化しました"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "無効"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "無効化に成功しました"
 
@@ -1410,6 +1409,10 @@ msgstr "このオプションは必要な場合以外は有効にしないでく
 msgid "Do you want to %{action} this site?"
 msgstr "このサイトを%{action}しますか?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "このストリームを%{action}しますか?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "自動証明書更新を無効にしますか?"
@@ -1483,7 +1486,7 @@ msgstr "一部のブラウザのセキュリティポリシーのため、localh
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "複製"
 
@@ -1532,13 +1535,10 @@ msgid "Email (*)"
 msgstr "メールアドレス (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "有効にする"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "有効にする"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FAが有効化されました"
@@ -1637,9 +1637,10 @@ msgstr "TOTP を有効にする"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "有効"
@@ -1647,7 +1648,7 @@ msgstr "有効"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "有効化に成功しました"
 
@@ -1884,7 +1885,7 @@ msgstr "データベースから証明書の削除に失敗しました: %{error
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "%{msg}の無効化に失敗しました"
 
@@ -1894,7 +1895,7 @@ msgstr "メンテナンスモードの無効化に失敗しました: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "有効化に失敗しました %{msg}"
 
@@ -2620,7 +2621,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "メンテナンス"
 
@@ -2649,7 +2650,7 @@ msgstr "設定管理"
 msgid "Manage Sites"
 msgstr "サイト管理"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "ストリームの管理"
 
@@ -2823,12 +2824,12 @@ msgstr "複数行ディレクティブ"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "名前"
 
@@ -3089,7 +3090,7 @@ msgstr "Nginx.conf には streams-enabled ディレクトリが含まれてい
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "いいえ"
 
@@ -3111,7 +3112,7 @@ msgid "Node"
 msgstr "ノード"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "ノードグループ"
@@ -3220,6 +3221,7 @@ msgstr "オフ"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3237,7 +3239,8 @@ msgstr "オフライン"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "OK"
 
@@ -3625,6 +3628,15 @@ msgstr "プロバイダー"
 msgid "Proxy"
 msgstr "プロキシ"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "プロキシパス"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "プロキシターゲット"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "公安番号"
@@ -4477,7 +4489,7 @@ msgstr "静的"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "ステータス"
 
@@ -5016,6 +5028,7 @@ msgstr "火曜日"
 msgid "Two-factor authentication required"
 msgstr "二要素認証が必要です"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5052,7 +5065,7 @@ msgstr "更新に成功しました"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5086,6 +5099,10 @@ msgstr "ファイルをアップロード"
 msgid "Upload Folders"
 msgstr "フォルダをアップロード"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "アップストリーム"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "アップストリーム名"
@@ -5358,6 +5375,12 @@ msgstr "以前のコードはもう使えません。"
 msgid "Your passkeys"
 msgstr "あなたのパスキー"
 
+#~ msgid "Disable"
+#~ msgstr "無効化"
+
+#~ msgid "Enable"
+#~ msgstr "有効にする"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "最後のバックアップエラー"
 

+ 55 - 35
app/src/language/ko_KR/app.po

@@ -142,7 +142,7 @@ msgstr "작업"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "작업"
@@ -160,7 +160,7 @@ msgstr "실제 작업자 대 구성 비율"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "추가"
 
@@ -187,11 +187,11 @@ msgstr "위치 추가"
 msgid "Add Site"
 msgstr "사이트 추가"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "스트림 추가"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "성공적으로 추가됨"
 
@@ -275,7 +275,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "정말 영구적으로 삭제하시겠습니까?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "정말 삭제하시겠습니까?"
 
@@ -566,6 +566,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "취소"
 
@@ -1179,7 +1180,7 @@ msgstr "공유 메모리 영역 이름과 크기를 정의합니다(예: proxy_c
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "삭제"
 
@@ -1227,7 +1228,7 @@ msgstr "%{node}에서 스트림 %{name} 삭제 실패"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "스트림 %{name}을(를) %{node}에서 성공적으로 삭제했습니다"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "스트림 삭제: %{stream_name}"
 
@@ -1301,13 +1302,10 @@ msgid "Directory path to store cache files"
 msgstr "캐시 파일을 저장할 디렉터리 경로"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "비활성화"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "비활성화"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name}의 자동 갱신 비활성화 실패"
@@ -1368,15 +1366,16 @@ msgstr "스트림 %{name}을(를) %{node}에서 비활성화했습니다"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "비활성화됨"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "성공적으로 비활성화됨"
 
@@ -1406,6 +1405,10 @@ msgstr "이 옵션은 필요한 경우가 아니라면 활성화하지 마세요
 msgid "Do you want to %{action} this site?"
 msgstr "이 사이트를 %{action}하시겠습니까?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "이 스트림을 %{action}하시겠습니까?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "자동 인증서 갱신을 비활성화하시겠습니까?"
@@ -1481,7 +1484,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "복제"
 
@@ -1530,13 +1533,10 @@ msgid "Email (*)"
 msgstr "이메일 (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "활성화"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "활성화"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA가 성공적으로 활성화되었습니다"
@@ -1635,9 +1635,10 @@ msgstr "TOTP 활성화"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "활성화됨"
@@ -1645,7 +1646,7 @@ msgstr "활성화됨"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "성공적으로 활성화됨"
 
@@ -1882,7 +1883,7 @@ msgstr "데이터베이스에서 인증서 삭제 실패: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "%{msg} 비활성화 실패"
 
@@ -1892,7 +1893,7 @@ msgstr "점검 모드 비활성화 실패: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "%{msg} 활성화 실패"
 
@@ -2617,7 +2618,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "유지보수"
 
@@ -2644,7 +2645,7 @@ msgstr "구성 관리"
 msgid "Manage Sites"
 msgstr "사이트 관리"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "스트림 관리"
 
@@ -2818,12 +2819,12 @@ msgstr "여러 줄 지시문"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "이름"
 
@@ -3084,7 +3085,7 @@ msgstr "Nginx.conf에는 streams-enabled 디렉토리가 포함됩니다"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "아니요"
 
@@ -3106,7 +3107,7 @@ msgid "Node"
 msgstr "노드"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "노드 그룹"
@@ -3215,6 +3216,7 @@ msgstr "끔"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3232,7 +3234,8 @@ msgstr "오프라인"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "확인"
 
@@ -3618,6 +3621,15 @@ msgstr "제공자"
 msgid "Proxy"
 msgstr "프록시"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "프록시 패스"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "프록시 대상"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "공공 보안 번호"
@@ -4472,7 +4484,7 @@ msgstr "정적"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "상태"
 
@@ -5009,6 +5021,7 @@ msgstr "화요일"
 msgid "Two-factor authentication required"
 msgstr "2단계 인증이 필요합니다"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5045,7 +5058,7 @@ msgstr "성공적으로 업데이트되었습니다"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5079,6 +5092,10 @@ msgstr "파일 업로드"
 msgid "Upload Folders"
 msgstr "폴더 업로드"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "업스트림"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "업스트림 이름"
@@ -5353,6 +5370,12 @@ msgstr "이전 코드는 더 이상 작동하지 않습니다."
 msgid "Your passkeys"
 msgstr "귀하의 패스키"
 
+#~ msgid "Disable"
+#~ msgstr "비활성화"
+
+#~ msgid "Enable"
+#~ msgstr "활성화"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "마지막 백업 오류"
 
@@ -5528,9 +5551,6 @@ msgstr "귀하의 패스키"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "%{conf_name}을(를) %{node_name}(으)로 성공적으로 복제함"
 
-#~ msgid "Target"
-#~ msgstr "대상"
-
 #~ msgid "File"
 #~ msgstr "파일"
 

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

@@ -131,8 +131,8 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129
-#: src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142
+#: src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr ""
@@ -151,7 +151,7 @@ msgstr ""
 #: src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr ""
 
@@ -180,11 +180,11 @@ msgstr ""
 msgid "Add Site"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr ""
 
@@ -269,7 +269,7 @@ msgstr ""
 
 #: src/language/curd.ts:25
 #: src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr ""
 
@@ -559,6 +559,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr ""
 
@@ -1125,7 +1126,7 @@ msgstr ""
 #: src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr ""
 
@@ -1175,7 +1176,7 @@ msgstr ""
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 
@@ -1250,13 +1251,10 @@ msgid "Directory path to store cache files"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr ""
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr ""
@@ -1319,16 +1317,17 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114
-#: src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127
+#: src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr ""
 
@@ -1358,6 +1357,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgstr ""
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr ""
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr ""
@@ -1430,7 +1433,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr ""
 
@@ -1480,13 +1483,10 @@ msgid "Email (*)"
 msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr ""
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr ""
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr ""
@@ -1587,10 +1587,11 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110
-#: src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123
+#: src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr ""
@@ -1598,7 +1599,7 @@ msgstr ""
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr ""
 
@@ -1836,7 +1837,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr ""
 
@@ -1846,7 +1847,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr ""
 
@@ -2548,7 +2549,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr ""
 
@@ -2577,7 +2578,7 @@ msgid "Manage Sites"
 msgstr ""
 
 #: src/routes/modules/streams.ts:10
-#: src/views/stream/StreamList.vue:101
+#: src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr ""
 
@@ -2754,12 +2755,12 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr ""
 
@@ -3024,7 +3025,7 @@ msgstr ""
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: src/views/site/site_list/SiteList.vue:98
-#: src/views/stream/StreamList.vue:155
+#: src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr ""
 
@@ -3046,8 +3047,8 @@ msgid "Node"
 msgstr ""
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63
-#: src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76
+#: src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr ""
@@ -3152,6 +3153,7 @@ msgstr ""
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3170,7 +3172,8 @@ msgstr ""
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr ""
 
@@ -3545,6 +3548,15 @@ msgstr ""
 msgid "Proxy"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr ""
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr ""
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr ""
@@ -4389,8 +4401,8 @@ msgstr ""
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88
-#: src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101
+#: src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr ""
 
@@ -4866,6 +4878,7 @@ msgstr ""
 msgid "Two-factor authentication required"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -4903,8 +4916,8 @@ msgstr ""
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81
-#: src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94
+#: src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -4939,6 +4952,10 @@ msgstr ""
 msgid "Upload Folders"
 msgstr ""
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr ""

+ 55 - 35
app/src/language/pt_PT/app.po

@@ -144,7 +144,7 @@ msgstr "Acção"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Ações"
@@ -162,7 +162,7 @@ msgstr "Rácio real de workers para configurado"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Adicionar"
 
@@ -189,11 +189,11 @@ msgstr "Adicionar Local"
 msgid "Add Site"
 msgstr "Adicionar Site"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Adicionar Stream"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Adicionado com sucesso"
 
@@ -279,7 +279,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Tem a certeza de que pretende eliminar permanentemente?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Tem certeza que pretende eliminar?"
 
@@ -576,6 +576,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Cancelar"
 
@@ -1216,7 +1217,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Eliminar"
 
@@ -1264,7 +1265,7 @@ msgstr "Falha ao eliminar o fluxo %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "O fluxo %{name} foi eliminado de %{node} com sucesso"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar stream: %{stream_name}"
 
@@ -1338,13 +1339,10 @@ msgid "Directory path to store cache files"
 msgstr "Caminho do diretório para armazenar ficheiros de cache"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Desativar"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Desativar"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Falha ao desativar a renovação automática para %{name}"
@@ -1405,15 +1403,16 @@ msgstr "Desativar o fluxo %{name} de %{node} com sucesso"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Desativado"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Desactivado com sucesso"
 
@@ -1443,6 +1442,10 @@ msgstr "Não ative esta opção a menos que tenha a certeza de que precisa dela.
 msgid "Do you want to %{action} this site?"
 msgstr "Deseja %{action} este site?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Deseja %{action} este fluxo?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Desactivar a renovação automática do certificado?"
@@ -1519,7 +1522,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Duplicado"
 
@@ -1568,13 +1571,10 @@ msgid "Email (*)"
 msgstr "E-mail (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "ativar"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Activar"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA Activado com Sucesso"
@@ -1673,9 +1673,10 @@ msgstr "Ativar TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Activado"
@@ -1683,7 +1684,7 @@ msgstr "Activado"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Activado com sucesso"
 
@@ -1920,7 +1921,7 @@ msgstr "Falha ao eliminar o certificado da base de dados: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Falha ao desactivar %{msg}"
 
@@ -1930,7 +1931,7 @@ msgstr "Falha ao desativar o modo de manutenção: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Falha ao Activar %{msg}"
 
@@ -2675,7 +2676,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Manutenção"
 
@@ -2704,7 +2705,7 @@ msgstr "Gerir Configurações"
 msgid "Manage Sites"
 msgstr "Gerir Sites"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Gerir Streams"
 
@@ -2878,12 +2879,12 @@ msgstr "Diretiva Multilinha"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Nome"
 
@@ -3146,7 +3147,7 @@ msgstr "Nginx.conf inclui o diretório streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Não"
 
@@ -3168,7 +3169,7 @@ msgid "Node"
 msgstr "Nó"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Grupo de nós"
@@ -3283,6 +3284,7 @@ msgstr "Desligado"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3300,7 +3302,8 @@ msgstr "Off-line"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "OK"
 
@@ -3704,6 +3707,15 @@ msgstr "Provedor"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Passe de Proxy"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Destinos do proxy"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Número de Segurança Pública"
@@ -4577,7 +4589,7 @@ msgstr "Estático"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Estado"
 
@@ -5157,6 +5169,7 @@ msgstr "Terça-feira"
 msgid "Two-factor authentication required"
 msgstr "Autenticação de dois fatores necessária"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5193,7 +5206,7 @@ msgstr "Atualização bem-sucedida"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5227,6 +5240,10 @@ msgstr "Carregar ficheiros"
 msgid "Upload Folders"
 msgstr "Carregar pastas"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "A montante"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Nome do Upstream"
@@ -5520,6 +5537,12 @@ msgstr "Os seus códigos antigos não funcionarão mais."
 msgid "Your passkeys"
 msgstr "As suas chaves de acesso"
 
+#~ msgid "Disable"
+#~ msgstr "Desativar"
+
+#~ msgid "Enable"
+#~ msgstr "Activar"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Último erro de backup"
 
@@ -5683,9 +5706,6 @@ msgstr "As suas chaves de acesso"
 #~ "Sincronização de configuração %{config_name} para %{env_name} falhou, "
 #~ "resposta: %{resp}"
 
-#~ msgid "Target"
-#~ msgstr "Destino"
-
 #~ msgid "The recovery code is only displayed once, please save it in a safe place."
 #~ msgstr ""
 #~ "O código de recuperação é apresentado apenas uma vez, guarde-o num local "

+ 55 - 35
app/src/language/ru_RU/app.po

@@ -148,7 +148,7 @@ msgstr "Действие"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Действия"
@@ -166,7 +166,7 @@ msgstr "Фактическое соотношение рабочих к наст
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Добавить"
 
@@ -193,11 +193,11 @@ msgstr "Добавить Location"
 msgid "Add Site"
 msgstr "Добавить Сайт"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Добавить поток"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Добавлено успешно"
 
@@ -281,7 +281,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Вы уверены, что хотите удалить безвозвратно?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Вы уверены, что хотите удалить?"
 
@@ -582,6 +582,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Отмена"
 
@@ -1217,7 +1218,7 @@ msgstr "Определите имя и размер зоны общей памя
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Удалить"
 
@@ -1265,7 +1266,7 @@ msgstr "Не удалось удалить поток %{name} с %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Поток %{name} успешно удален с %{node}"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Удалить поток: %{stream_name}"
 
@@ -1339,13 +1340,10 @@ msgid "Directory path to store cache files"
 msgstr "Путь к каталогу для хранения кэшированных файлов"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Отключить"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Отключить"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
@@ -1406,15 +1404,16 @@ msgstr "Поток %{name} отключен от %{node} успешно"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Отключено"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Отключено успешно"
 
@@ -1444,6 +1443,10 @@ msgstr "Не включайте эту опцию, если не уверены,
 msgid "Do you want to %{action} this site?"
 msgstr "Вы хотите %{action} этот сайт?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Вы хотите %{action} этот поток?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Вы хотите отключить автоматическое обновление сертификата?"
@@ -1520,7 +1523,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Дублировать"
 
@@ -1569,13 +1572,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "включить"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Включить"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "Двухфакторная аутентификация успешно включена"
@@ -1674,9 +1674,10 @@ msgstr "Включить TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Включено"
@@ -1684,7 +1685,7 @@ msgstr "Включено"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Активировано успешно"
 
@@ -1921,7 +1922,7 @@ msgstr "Не удалось удалить сертификат из базы д
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Не удалось отключить %{msg}"
 
@@ -1931,7 +1932,7 @@ msgstr "Не удалось отключить режим обслуживани
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Не удалось включить %{msg}"
 
@@ -2674,7 +2675,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Техническое обслуживание"
 
@@ -2703,7 +2704,7 @@ msgstr "Конфигурации"
 msgid "Manage Sites"
 msgstr "Сайты"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Управление потоками"
 
@@ -2877,12 +2878,12 @@ msgstr "Многострочная директива"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Имя"
 
@@ -3145,7 +3146,7 @@ msgstr "Nginx.conf включает каталог streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Нет"
 
@@ -3167,7 +3168,7 @@ msgid "Node"
 msgstr "Узел"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Группа узлов"
@@ -3282,6 +3283,7 @@ msgstr "Выкл"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3299,7 +3301,8 @@ msgstr "Оффлайн"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "ОК"
 
@@ -3708,6 +3711,15 @@ msgstr "Провайдер"
 msgid "Proxy"
 msgstr "Прокси"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Прокси-передача"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Цели прокси"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Номер в органах общественной безопасности"
@@ -4573,7 +4585,7 @@ msgstr "Статический"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Статус"
 
@@ -5154,6 +5166,7 @@ msgstr "Вторник"
 msgid "Two-factor authentication required"
 msgstr "Требуется двухфакторная аутентификация"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5190,7 +5203,7 @@ msgstr "Успешно обновлено"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5224,6 +5237,10 @@ msgstr "Загрузить файлы"
 msgid "Upload Folders"
 msgstr "Загрузить папки"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Восходящий поток"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Имя Upstream"
@@ -5516,6 +5533,12 @@ msgstr "Ваши старые коды больше не будут работа
 msgid "Your passkeys"
 msgstr "Ваши ключи доступа"
 
+#~ msgid "Disable"
+#~ msgstr "Отключить"
+
+#~ msgid "Enable"
+#~ msgstr "Включить"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Ошибка последнего резервного копирования"
 
@@ -5703,9 +5726,6 @@ msgstr "Ваши ключи доступа"
 #~ "Синхронизация конфигурации %{config_name} с %{env_name} не удалась, ответ: "
 #~ "%{resp}"
 
-#~ msgid "Target"
-#~ msgstr "Цель"
-
 #~ msgid "File"
 #~ msgstr "Файл"
 

+ 55 - 36
app/src/language/tr_TR/app.po

@@ -144,7 +144,7 @@ msgstr "Eylem"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "İşlemler"
@@ -162,7 +162,7 @@ msgstr "Gerçek çalışanın yapılandırılmışa oranı"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Ekle"
 
@@ -189,11 +189,11 @@ msgstr "Konum ekle"
 msgid "Add Site"
 msgstr "Site Ekle"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Akış Ekle"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Başarıyla eklendi"
 
@@ -277,7 +277,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Kalıcı olarak silmek istediğinizden emin misiniz?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Silmek istediğine emin misin?"
 
@@ -572,6 +572,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "İptal"
 
@@ -1211,7 +1212,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Sil"
 
@@ -1259,7 +1260,7 @@ msgstr "%{node} üzerindeki %{name} akışı silinemedi"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "%{name} akışı %{node} üzerinden başarıyla silindi"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Akışı sil: %{stream_name}"
 
@@ -1333,13 +1334,10 @@ msgid "Directory path to store cache files"
 msgstr "Önbellek dosyalarını depolamak için dizin yolu"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Devre dışı bırak"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Devre Dışı Bırak"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name} için otomatik yenileme devre dışı bırakılamadı"
@@ -1400,15 +1398,16 @@ msgstr "Akış %{name}, %{node} üzerinden başarıyla devre dışı bırakıld
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Devre dışı"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Başarıyla devre dışı bırakıldı"
 
@@ -1438,6 +1437,10 @@ msgstr "Bu seçeneği, ihtiyacınız olduğundan emin olmadıkça etkinleştirme
 msgid "Do you want to %{action} this site?"
 msgstr "Bu siteyi %{action} etmek istiyor musunuz?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Bu akışı %{action} etmek istiyor musunuz?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Otomatik sertifika yenilemeyi devre dışı bırakmak istiyor musunuz?"
@@ -1516,7 +1519,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Kopyala"
 
@@ -1565,13 +1568,10 @@ msgid "Email (*)"
 msgstr "E-posta(*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "etkinleştir"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Etkinleştir"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA'yı başarıyla etkinleştirildi"
@@ -1670,9 +1670,10 @@ msgstr "TOTP'yi Etkinleştir"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Etkin"
@@ -1680,7 +1681,7 @@ msgstr "Etkin"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Başarıyla etkinleştirildi"
 
@@ -1917,7 +1918,7 @@ msgstr "Sertifika veritabanından silinemedi: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Devre dışı bırakılamadı %{msg}"
 
@@ -1927,7 +1928,7 @@ msgstr "Bakım modu devre dışı bırakılamadı: %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Etkinleştirilemedi %{msg}"
 
@@ -2673,7 +2674,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Bakım"
 
@@ -2702,7 +2703,7 @@ msgstr "Yapılandırmaları Yönet"
 msgid "Manage Sites"
 msgstr "Siteleri Yönet"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Akışları Yönet"
 
@@ -2876,12 +2877,12 @@ msgstr "Çok Satırlı Yönergeler"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "İsim"
 
@@ -3144,7 +3145,7 @@ msgstr "Nginx.conf, streams-enabled dizinini içerir"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Hayır"
 
@@ -3166,7 +3167,7 @@ msgid "Node"
 msgstr "Düğüm"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Düğüm Grubu"
@@ -3281,6 +3282,7 @@ msgstr "Kapalı"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3298,7 +3300,8 @@ msgstr "Çevrimdışı"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "Tamam"
 
@@ -3702,6 +3705,15 @@ msgstr "Sağlayıcı"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Proxy Geçişi"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Proxy Hedefleri"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Kamu Güvenlik Numarası"
@@ -4574,7 +4586,7 @@ msgstr "Statik"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Durum"
 
@@ -5156,6 +5168,7 @@ msgstr "Salı"
 msgid "Two-factor authentication required"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5192,7 +5205,7 @@ msgstr "Başarıyla güncellendi"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5226,6 +5239,10 @@ msgstr "Dosyaları Yükle"
 msgid "Upload Folders"
 msgstr "Klasörleri Yükle"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Yukarı Akış"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Yukarı Akış Adı"
@@ -5520,6 +5537,12 @@ msgstr "Eski kodlarınız artık çalışmayacak."
 msgid "Your passkeys"
 msgstr "Geçiş Anahtarlarınız"
 
+#~ msgid "Disable"
+#~ msgstr "Devre Dışı Bırak"
+
+#~ msgid "Enable"
+#~ msgstr "Etkinleştir"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Son Yedekleme Hatası"
 
@@ -5710,10 +5733,6 @@ msgstr "Geçiş Anahtarlarınız"
 #~ "Yapılandırma %{config_name} ile %{env_name} arasında eşitleme başarısız "
 #~ "oldu, yanıt: %{resp}"
 
-#, fuzzy
-#~ msgid "Target"
-#~ msgstr "Hedef"
-
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "Tarayamıyor musunuz? Metin anahtar bağlamasını kullanın"
 

+ 55 - 32
app/src/language/uk_UA/app.po

@@ -148,7 +148,7 @@ msgstr "Дія"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Дії"
@@ -166,7 +166,7 @@ msgstr "Фактичне співвідношення робочих до нал
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Додати"
 
@@ -193,11 +193,11 @@ msgstr "Додати локацію"
 msgid "Add Site"
 msgstr "Додати сайт"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Додати стрім"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Успішно додано"
 
@@ -281,7 +281,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Ви впевнені, що хочете видалити назавжди?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Ви впевнені, що хочете видалити?"
 
@@ -579,6 +579,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Скасувати"
 
@@ -1247,7 +1248,7 @@ msgstr "Вкажіть назву та розмір зони спільної п
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Видалити"
 
@@ -1295,7 +1296,7 @@ msgstr "Не вдалося видалити потік %{name} з %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Потік %{name} успішно видалено з %{node}"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Видалити потік: %{stream_name}"
 
@@ -1405,13 +1406,10 @@ msgid "Directory path to store cache files"
 msgstr "Шлях до каталогу для зберігання файлів кешу"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Вимкнути"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Вимкнути"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не вдалося вимкнути автоматичне поновлення для %{name}"
@@ -1472,15 +1470,16 @@ msgstr "Потік %{name} успішно вимкнено з %{node}"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Вимкнено"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Успішно вимкнено"
 
@@ -1510,6 +1509,10 @@ msgstr "Не вмикайте цю опцію, якщо ви не впевнен
 msgid "Do you want to %{action} this site?"
 msgstr "Ви хочете %{action} цей сайт?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Ви хочете %{action} цей потік?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Ви хочете вимкнути автоматичне оновлення сертифіката?"
@@ -1586,7 +1589,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Дублювати"
 
@@ -1635,13 +1638,10 @@ msgid "Email (*)"
 msgstr "Електронна пошта (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "увімкнути"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Увімкнути"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "2FA успішно ввімкнено"
@@ -1740,9 +1740,10 @@ msgstr "Увімкнути TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Увімкнено"
@@ -1750,7 +1751,7 @@ msgstr "Увімкнено"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Успішно ввімкнено"
 
@@ -1987,7 +1988,7 @@ msgstr "Не вдалося видалити сертифікат з бази д
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Не вдалося вимкнути %{msg}"
 
@@ -1997,7 +1998,7 @@ msgstr "Не вдалося вимкнути режим обслуговуван
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Не вдалося увімкнути %{msg}"
 
@@ -2740,7 +2741,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Технічне обслуговування"
 
@@ -2769,7 +2770,7 @@ msgstr "Керування конфігураціями"
 msgid "Manage Sites"
 msgstr "Керування сайтами"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Керування потоками"
 
@@ -2943,12 +2944,12 @@ msgstr "Багаторядкова директива"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Ім'я"
 
@@ -3211,7 +3212,7 @@ msgstr "Nginx.conf включає каталог streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Ні"
 
@@ -3233,7 +3234,7 @@ msgid "Node"
 msgstr "Вузол"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Група вузлів"
@@ -3348,6 +3349,7 @@ msgstr "Вимкнено"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3365,7 +3367,8 @@ msgstr "Офлайн"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "Гаразд"
 
@@ -3770,6 +3773,15 @@ msgstr "Провайдер"
 msgid "Proxy"
 msgstr "Проксі"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Проксі-передача"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Цілі проксі"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Номер громадської безпеки"
@@ -4642,7 +4654,7 @@ msgstr "Статичний"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Статус"
 
@@ -5222,6 +5234,7 @@ msgstr "Вівторок"
 msgid "Two-factor authentication required"
 msgstr "Потрібна двофакторна аутентифікація"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5258,7 +5271,7 @@ msgstr "Успішно оновлено"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5292,6 +5305,10 @@ msgstr "Завантажити файли"
 msgid "Upload Folders"
 msgstr "Завантажити папки"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Вгору за течією"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Назва апстріму"
@@ -5583,6 +5600,12 @@ msgstr "Ваші старі коди більше не працюватимут
 msgid "Your passkeys"
 msgstr "Ваші ключі доступу"
 
+#~ msgid "Disable"
+#~ msgstr "Вимкнути"
+
+#~ msgid "Enable"
+#~ msgstr "Увімкнути"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Помилка останнього резервного копіювання"
 

+ 55 - 35
app/src/language/vi_VN/app.po

@@ -139,7 +139,7 @@ msgstr "Hành động"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "Hành động"
@@ -157,7 +157,7 @@ msgstr "Tỷ lệ công nhân thực tế so với cấu hình"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "Thêm"
 
@@ -184,11 +184,11 @@ msgstr "Thêm Location"
 msgid "Add Site"
 msgstr "Thêm Website"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "Thêm luồng"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "Đã thêm thành công"
 
@@ -272,7 +272,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Bạn có chắc chắn muốn xóa vĩnh viễn không?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "Bạn có chắc chắn muốn xóa không?"
 
@@ -563,6 +563,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "Huỷ"
 
@@ -1191,7 +1192,7 @@ msgstr "Xác định tên và kích thước vùng bộ nhớ dùng chung, ví d
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "Xoá"
 
@@ -1239,7 +1240,7 @@ msgstr "Xóa luồng %{name} từ %{node} thất bại"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Đã xóa luồng %{name} từ %{node} thành công"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "Xóa luồng: %{stream_name}"
 
@@ -1313,13 +1314,10 @@ msgid "Directory path to store cache files"
 msgstr "Đường dẫn thư mục để lưu trữ các tệp bộ nhớ đệm"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "Vô hiệu hóa"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Vô hiệu hóa"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Vô hiệu hóa gia hạn tự động thất bại cho %{name}"
@@ -1380,15 +1378,16 @@ msgstr "Đã vô hiệu hóa luồng %{name} từ %{node} thành công"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "Đã tắt"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "Đã tắt thành công"
 
@@ -1418,6 +1417,10 @@ msgstr "Không bật tùy chọn này trừ khi bạn chắc chắn cần đến
 msgid "Do you want to %{action} this site?"
 msgstr "Bạn có muốn %{action} trang web này không?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "Bạn có muốn %{action} luồng này không?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Bạn muốn tắt tự động gia hạn chứng chỉ SSL ?"
@@ -1495,7 +1498,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "Nhân bản"
 
@@ -1544,13 +1547,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "bật"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Bật"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "Bật 2FA thành công"
@@ -1649,9 +1649,10 @@ msgstr "Bật TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "Đã bật"
@@ -1659,7 +1660,7 @@ msgstr "Đã bật"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "Đã bật"
 
@@ -1896,7 +1897,7 @@ msgstr "Xóa chứng chỉ từ cơ sở dữ liệu thất bại: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "Không thể tắt %{msg}"
 
@@ -1906,7 +1907,7 @@ msgstr "Không thể tắt chế độ bảo trì %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "Không thể bật %{msg}"
 
@@ -2648,7 +2649,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "Bảo trì"
 
@@ -2677,7 +2678,7 @@ msgstr "Quản lý cấu hình"
 msgid "Manage Sites"
 msgstr "Quản lý Website"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "Quản lý luồng"
 
@@ -2851,12 +2852,12 @@ msgstr "Chỉ thị nhiều dòng"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "Tên"
 
@@ -3119,7 +3120,7 @@ msgstr "Nginx.conf bao gồm thư mục streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "Không"
 
@@ -3141,7 +3142,7 @@ msgid "Node"
 msgstr "Nút"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "Nhóm nút"
@@ -3254,6 +3255,7 @@ msgstr "Tắt"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3271,7 +3273,8 @@ msgstr "Ngoại tuyến"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "Đồng ý"
 
@@ -3667,6 +3670,15 @@ msgstr "Nhà cung cấp"
 msgid "Proxy"
 msgstr "Proxy"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "Chuyển tiếp Proxy"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "Mục tiêu proxy"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "Số An ninh Công cộng"
@@ -4530,7 +4542,7 @@ msgstr "Tĩnh"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "Trạng thái"
 
@@ -5110,6 +5122,7 @@ msgstr "Thứ Ba"
 msgid "Two-factor authentication required"
 msgstr "Yêu cầu xác thực hai yếu tố"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5146,7 +5159,7 @@ msgstr "Cập nhật thành công"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5180,6 +5193,10 @@ msgstr "Tải lên tệp"
 msgid "Upload Folders"
 msgstr "Tải lên thư mục"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Ngược dòng"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Tên Upstream"
@@ -5469,6 +5486,12 @@ msgstr "Mã cũ của bạn sẽ không còn hoạt động nữa."
 msgid "Your passkeys"
 msgstr "Khóa truy cập của bạn"
 
+#~ msgid "Disable"
+#~ msgstr "Vô hiệu hóa"
+
+#~ msgid "Enable"
+#~ msgstr "Bật"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "Lỗi sao lưu cuối cùng"
 
@@ -5642,9 +5665,6 @@ msgstr "Khóa truy cập của bạn"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 
-#~ msgid "Target"
-#~ msgstr "Mục tiêu"
-
 #~ msgid "File"
 #~ msgstr "Tệp tin"
 

+ 55 - 35
app/src/language/zh_CN/app.po

@@ -143,7 +143,7 @@ msgstr "操作"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "操作"
@@ -161,7 +161,7 @@ msgstr "实际工作进程与配置比例"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "添加"
 
@@ -188,11 +188,11 @@ msgstr "添加 Location"
 msgid "Add Site"
 msgstr "添加站点"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "添加 Stream"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "添加成功"
 
@@ -276,7 +276,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "确定要永久删除吗?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "您确定要删除吗?"
 
@@ -565,6 +565,7 @@ msgstr "基于 worker_processes * worker_connections 计算得出。实际性能
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "取消"
 
@@ -1173,7 +1174,7 @@ msgstr "定义共享内存区名称和大小,例如 proxy_cache:10m"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "删除"
 
@@ -1221,7 +1222,7 @@ msgstr "从 %{node} 删除 Stream %{name} 失败"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "已成功从 %{node} 删除 Stream %{name}"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 
@@ -1295,13 +1296,10 @@ msgid "Directory path to store cache files"
 msgstr "存储缓存文件的目录路径"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "禁用"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "禁用"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "禁用 %{name} 的自动续期失败"
@@ -1362,15 +1360,16 @@ msgstr "在 %{node} 上禁用 %{name} 成功"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "禁用"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "禁用成功"
 
@@ -1400,6 +1399,10 @@ msgstr "除非确定需要,否则不要启用该选项。"
 msgid "Do you want to %{action} this site?"
 msgstr "您想将这个网站%{action}吗?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "您要%{action}此流吗?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "你想禁用自动更新证书吗?"
@@ -1473,7 +1476,7 @@ msgstr "由于某些浏览器的安全策略,除非在 localhost 上使用,
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "复制"
 
@@ -1522,13 +1525,10 @@ msgid "Email (*)"
 msgstr "邮箱 (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "启用"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "启用"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "二步验证启用成功"
@@ -1627,9 +1627,10 @@ msgstr "启用 TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "启用"
@@ -1637,7 +1638,7 @@ msgstr "启用"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "启用成功"
 
@@ -1874,7 +1875,7 @@ msgstr "从数据库中删除证书失败:%{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "禁用失败 %{msg}"
 
@@ -1884,7 +1885,7 @@ msgstr "停用维护模式失败 %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "启用失败 %{msg}"
 
@@ -2609,7 +2610,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "维护模式"
 
@@ -2636,7 +2637,7 @@ msgstr "配置管理"
 msgid "Manage Sites"
 msgstr "网站管理"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "管理 Stream"
 
@@ -2810,12 +2811,12 @@ msgstr "多行指令"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "名称"
 
@@ -3076,7 +3077,7 @@ msgstr "检查 nginx.conf 是否包含 streams-enabled 的目录"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "取消"
 
@@ -3098,7 +3099,7 @@ msgid "Node"
 msgstr "节点"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "节点组"
@@ -3207,6 +3208,7 @@ msgstr "关闭"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3224,7 +3226,8 @@ msgstr "离线"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "确定"
 
@@ -3608,6 +3611,15 @@ msgstr "提供商"
 msgid "Proxy"
 msgstr "代理"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "代理传递"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "代理目标"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "公安备案号"
@@ -4460,7 +4472,7 @@ msgstr "静态"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "状态"
 
@@ -4989,6 +5001,7 @@ msgstr "星期二"
 msgid "Two-factor authentication required"
 msgstr "需要两步验证"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5025,7 +5038,7 @@ msgstr "更新成功"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5059,6 +5072,10 @@ msgstr "上传文件"
 msgid "Upload Folders"
 msgstr "上传文件夹"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "上游"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Upstream 名称"
@@ -5329,6 +5346,12 @@ msgstr "您的旧代码将不再有效。"
 msgid "Your passkeys"
 msgstr "你的 Passkeys"
 
+#~ msgid "Disable"
+#~ msgstr "禁用"
+
+#~ msgid "Enable"
+#~ msgstr "启用"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "最后一次备份错误"
 
@@ -5546,9 +5569,6 @@ msgstr "你的 Passkeys"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "同步配置 %{config_name} 到 %{env_name} 失败,响应:%{resp}"
 
-#~ msgid "Target"
-#~ msgstr "目标"
-
 #~ msgid ""
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "2FA."

+ 55 - 35
app/src/language/zh_TW/app.po

@@ -147,7 +147,7 @@ msgstr "操作"
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
-#: src/views/site/site_list/columns.tsx:129 src/views/stream/columns.tsx:64
+#: src/views/site/site_list/columns.tsx:142 src/views/stream/columns.tsx:105
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgstr "操作"
@@ -165,7 +165,7 @@ msgstr "實際工作進程與配置比例"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgstr "新增"
 
@@ -192,11 +192,11 @@ msgstr "新增 Location"
 msgid "Add Site"
 msgstr "新增網站"
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgstr "新增 Stream"
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgstr "新增成功"
 
@@ -280,7 +280,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "確定要永久刪除嗎?"
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
-#: src/views/stream/StreamList.vue:157
+#: src/views/stream/StreamList.vue:121
 msgid "Are you sure you want to delete?"
 msgstr "您確定要刪除嗎?"
 
@@ -569,6 +569,7 @@ msgstr "基於 worker_processes * worker_connections 計算得出。實際效能
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgstr "取消"
 
@@ -1177,7 +1178,7 @@ msgstr "定義共享記憶體區域名稱和大小,例如 proxy_cache:10m"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgstr "刪除"
 
@@ -1225,7 +1226,7 @@ msgstr "從 %{node} 刪除串流 %{name} 失敗"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "已成功從 %{node} 刪除串流 %{name}"
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgstr "刪除 Stream:%{stream_name}"
 
@@ -1299,13 +1300,10 @@ msgid "Directory path to store cache files"
 msgstr "儲存快取檔案的目錄路徑"
 
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgstr "禁用"
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "停用"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "停用 %{name} 的自動續期失敗"
@@ -1366,15 +1364,16 @@ msgstr "已成功從 %{node} 停用串流 %{name}"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
-#: src/views/site/site_list/columns.tsx:114 src/views/stream/columns.tsx:48
+#: src/views/site/site_list/columns.tsx:127 src/views/stream/columns.tsx:94
 #: src/views/stream/components/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgstr "停用"
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgstr "成功停用"
 
@@ -1404,6 +1403,10 @@ msgstr "除非您確定需要,否則不要啟用此選項。"
 msgid "Do you want to %{action} this site?"
 msgstr "您想要%{action}這個網站嗎?"
 
+#: src/views/stream/components/StreamStatusSelect.vue:61
+msgid "Do you want to %{action} this stream?"
+msgstr "您要%{action}此串流嗎?"
+
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "您要停用自動憑證續訂嗎?"
@@ -1477,7 +1480,7 @@ msgstr "基於部分瀏覽器的安全政策,您無法在未啟用 HTTPS 網
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgstr "複製"
 
@@ -1526,13 +1529,10 @@ msgid "Email (*)"
 msgstr "電子郵件 (*)"
 
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgstr "啟用"
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "啟用"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgstr "啟用多因素身份驗證成功"
@@ -1631,9 +1631,10 @@ msgstr "啟用 TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
-#: src/views/site/site_list/columns.tsx:110 src/views/stream/columns.tsx:44
+#: src/views/site/site_list/columns.tsx:123 src/views/stream/columns.tsx:90
 #: src/views/stream/components/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgstr "已啟用"
@@ -1641,7 +1642,7 @@ msgstr "已啟用"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgstr "成功啟用"
 
@@ -1878,7 +1879,7 @@ msgstr "從資料庫刪除憑證失敗: %{error}"
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
-#: src/views/stream/StreamList.vue:60
+#: src/views/stream/components/StreamStatusSelect.vue:45
 msgid "Failed to disable %{msg}"
 msgstr "停用 %{msg} 失敗"
 
@@ -1888,7 +1889,7 @@ msgstr "無法停用維護模式 %{msg}"
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
-#: src/views/stream/StreamList.vue:50
+#: src/views/stream/components/StreamStatusSelect.vue:32
 msgid "Failed to enable %{msg}"
 msgstr "啟用 %{msg} 失敗"
 
@@ -2613,7 +2614,7 @@ msgstr ""
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
-#: src/views/site/site_list/columns.tsx:118
+#: src/views/site/site_list/columns.tsx:131
 msgid "Maintenance"
 msgstr "維護"
 
@@ -2640,7 +2641,7 @@ msgstr "管理設定"
 msgid "Manage Sites"
 msgstr "管理網站"
 
-#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:101
+#: src/routes/modules/streams.ts:10 src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgstr "管理 Stream"
 
@@ -2814,12 +2815,12 @@ msgstr "多行指令"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
-#: src/views/site/site_list/columns.tsx:15
+#: src/views/site/site_list/columns.tsx:16
 #: src/views/site/site_list/SiteDuplicate.vue:79
-#: src/views/stream/columns.tsx:10
+#: src/views/stream/columns.tsx:12
 #: src/views/stream/components/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgstr "名稱"
 
@@ -3080,7 +3081,7 @@ msgstr "Nginx.conf 包含 streams-enabled 目錄"
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
-#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:155
+#: src/views/site/site_list/SiteList.vue:98 src/views/stream/StreamList.vue:119
 msgid "No"
 msgstr "取消"
 
@@ -3102,7 +3103,7 @@ msgid "Node"
 msgstr "節點"
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
-#: src/views/site/site_list/columns.tsx:63 src/views/stream/columns.tsx:20
+#: src/views/site/site_list/columns.tsx:76 src/views/stream/columns.tsx:44
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgstr "節點群組"
@@ -3211,6 +3212,7 @@ msgstr "關"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
@@ -3228,7 +3230,8 @@ msgstr "離線"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
-#: src/views/stream/StreamList.vue:156
+#: src/views/stream/components/StreamStatusSelect.vue:64
+#: src/views/stream/StreamList.vue:120
 msgid "OK"
 msgstr "確定"
 
@@ -3614,6 +3617,15 @@ msgstr "供應商"
 msgid "Proxy"
 msgstr "代理伺服器"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Proxy Pass"
+msgstr "代理傳遞"
+
+#: src/views/site/site_list/columns.tsx:64
+#: src/views/stream/columns.tsx:32
+msgid "Proxy Targets"
+msgstr "代理目標"
+
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgstr "公安編號"
@@ -4466,7 +4478,7 @@ msgstr "靜態"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
-#: src/views/site/site_list/columns.tsx:88 src/views/stream/columns.tsx:37
+#: src/views/site/site_list/columns.tsx:101 src/views/stream/columns.tsx:69
 msgid "Status"
 msgstr "狀態"
 
@@ -4995,6 +5007,7 @@ msgstr "星期二"
 msgid "Two-factor authentication required"
 msgstr "需要多重因素驗證"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5031,7 +5044,7 @@ msgstr "更新成功"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
-#: src/views/site/site_list/columns.tsx:81 src/views/stream/columns.tsx:57
+#: src/views/site/site_list/columns.tsx:94 src/views/stream/columns.tsx:62
 #: src/views/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
@@ -5065,6 +5078,10 @@ msgstr "上傳檔案"
 msgid "Upload Folders"
 msgstr "上傳資料夾"
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "上游"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgstr "Upstream 名稱"
@@ -5335,6 +5352,12 @@ msgstr "您的舊代碼將不再有效。"
 msgid "Your passkeys"
 msgstr "您的通行金鑰"
 
+#~ msgid "Disable"
+#~ msgstr "停用"
+
+#~ msgid "Enable"
+#~ msgstr "啟用"
+
 #~ msgid "Last Backup Error"
 #~ msgstr "最後一次備份錯誤"
 
@@ -5524,9 +5547,6 @@ msgstr "您的通行金鑰"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "同步設定 %{config_name} 到 %{env_name} 失敗,回應:%{resp}"
 
-#~ msgid "Target"
-#~ msgstr "目標"
-
 #~ msgid ""
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "2FA."

+ 2 - 0
app/src/pinia/index.ts

@@ -1,9 +1,11 @@
 import { useGlobalStore } from './moudule/global'
+import { useProxyAvailabilityStore } from './moudule/proxyAvailability'
 import { useSettingsStore } from './moudule/settings'
 import { useUserStore } from './moudule/user'
 
 export {
   useGlobalStore,
+  useProxyAvailabilityStore,
   useSettingsStore,
   useUserStore,
 }

+ 142 - 0
app/src/pinia/moudule/proxyAvailability.ts

@@ -0,0 +1,142 @@
+import type ReconnectingWebSocket from 'reconnecting-websocket'
+import type { ProxyTarget } from '@/api/site'
+import { debounce } from 'lodash'
+import { defineStore } from 'pinia'
+import upstream from '@/api/upstream'
+
+export interface ProxyAvailabilityResult {
+  online: boolean
+  latency: number
+}
+
+export const useProxyAvailabilityStore = defineStore('proxyAvailability', () => {
+  const availabilityResults = ref<Record<string, ProxyAvailabilityResult>>({})
+  const websocket = shallowRef<ReconnectingWebSocket | WebSocket>()
+  const isConnected = ref(false)
+
+  // Map to store targets for each component instance
+  const componentTargets = ref<Map<string, string[]>>(new Map())
+
+  // Computed property to get unique targets from all components
+  const allTargets = computed(() => {
+    const allTargetsList: string[] = []
+    componentTargets.value.forEach(targets => {
+      allTargetsList.push(...targets)
+    })
+    return [...new Set(allTargetsList)]
+  })
+
+  function getTargetKey(target: ProxyTarget): string {
+    return `${target.host}:${target.port}`
+  }
+
+  // Debounced function to update targets on server
+  const debouncedUpdateTargets = debounce(() => {
+    if (websocket.value && isConnected.value) {
+      websocket.value.send(JSON.stringify(allTargets.value))
+    }
+  }, 300)
+
+  function ensureWebSocketConnection() {
+    if (websocket.value && isConnected.value) {
+      return
+    }
+
+    // Close existing connection if any
+    if (websocket.value) {
+      websocket.value.close()
+    }
+
+    // Create new WebSocket connection
+    websocket.value = upstream.availability_test()
+
+    websocket.value.onopen = () => {
+      isConnected.value = true
+      // Send current targets immediately after connection
+      debouncedUpdateTargets()
+    }
+
+    websocket.value.onmessage = (e: MessageEvent) => {
+      const results = JSON.parse(e.data) as Record<string, ProxyAvailabilityResult>
+      // Update availability results
+      Object.assign(availabilityResults.value, results)
+    }
+
+    websocket.value.onclose = () => {
+      isConnected.value = false
+    }
+
+    websocket.value.onerror = error => {
+      console.error('WebSocket error:', error)
+      isConnected.value = false
+    }
+  }
+
+  function registerComponent(targets: ProxyTarget[]): string {
+    const componentId = useId()
+    const targetKeys = targets.map(getTargetKey)
+
+    componentTargets.value.set(componentId, targetKeys)
+
+    // Ensure WebSocket connection exists
+    ensureWebSocketConnection()
+
+    // Update targets on server (debounced)
+    debouncedUpdateTargets()
+
+    return componentId
+  }
+
+  function updateComponentTargets(componentId: string, targets: ProxyTarget[]) {
+    const targetKeys = targets.map(getTargetKey)
+    componentTargets.value.set(componentId, targetKeys)
+
+    // Update targets on server (debounced)
+    debouncedUpdateTargets()
+  }
+
+  function unregisterComponent(componentId: string) {
+    componentTargets.value.delete(componentId)
+
+    // Update targets on server (debounced)
+    debouncedUpdateTargets()
+
+    // Close WebSocket if no components are registered
+    if (componentTargets.value.size === 0) {
+      // Cancel pending debounced calls
+      debouncedUpdateTargets.cancel()
+
+      if (websocket.value) {
+        websocket.value.close()
+        websocket.value = undefined
+        isConnected.value = false
+      }
+    }
+  }
+
+  function getAvailabilityResult(target: ProxyTarget): ProxyAvailabilityResult | undefined {
+    const key = getTargetKey(target)
+    return availabilityResults.value[key]
+  }
+
+  function isTargetTesting(target: ProxyTarget): boolean {
+    const key = getTargetKey(target)
+    return allTargets.value.includes(key)
+  }
+
+  // Watch for changes in allTargets and update server (debounced)
+  watch(allTargets, () => {
+    debouncedUpdateTargets()
+  })
+
+  return {
+    availabilityResults: readonly(availabilityResults),
+    isConnected: readonly(isConnected),
+    registerComponent,
+    updateComponentTargets,
+    unregisterComponent,
+    getAvailabilityResult,
+    isTargetTesting,
+    getTargetKey,
+  }
+})

+ 15 - 2
app/src/views/site/site_list/columns.tsx

@@ -7,6 +7,7 @@ import type { JSXElements } from '@/types'
 import { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import env_group from '@/api/env_group'
+import ProxyTargets from '@/components/ProxyTargets'
 import { ConfigStatus } from '@/constants'
 import envGroupColumns from '@/views/environments/group/columns'
 import SiteStatusSelect from '@/views/site/components/SiteStatusSelect.vue'
@@ -53,12 +54,24 @@ const columns: StdTableColumn[] = [{
       }
 
       template.push(
-        <div style="display: flex; flex-wrap: wrap;">{urlsContainer}</div>,
+        <div style="display: flex; flex-wrap: wrap; margin-bottom: 4px;">{urlsContainer}</div>,
       )
     }
 
     return h('div', {}, template)
   },
+}, {
+  title: () => $gettext('Proxy Targets'),
+  dataIndex: 'proxy_targets',
+  width: 200,
+  customRender: ({ record }: CustomRenderArgs) => {
+    if (record.proxy_targets && record.proxy_targets.length > 0) {
+      return h(ProxyTargets, {
+        targets: record.proxy_targets,
+      })
+    }
+    return h('span', '-')
+  },
 }, {
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
@@ -123,7 +136,7 @@ const columns: StdTableColumn[] = [{
   },
   sorter: true,
   pure: true,
-  width: 50,
+  width: 100,
   fixed: 'right',
 }, {
   title: () => $gettext('Actions'),

+ 0 - 36
app/src/views/stream/StreamList.vue

@@ -41,26 +41,6 @@ watch(route, () => {
   inspect_config.value?.test()
 })
 
-function enable(name: string) {
-  stream.enable(name).then(() => {
-    message.success($gettext('Enabled successfully'))
-    curd.value?.refresh()
-    inspect_config.value?.test()
-  }).catch(r => {
-    message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
-  })
-}
-
-function disable(name: string) {
-  stream.disable(name).then(() => {
-    message.success($gettext('Disabled successfully'))
-    curd.value?.refresh()
-    inspect_config.value?.test()
-  }).catch(r => {
-    message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
-  })
-}
-
 function destroy(stream_name: string) {
   stream.deleteItem(stream_name).then(() => {
     curd.value.refresh()
@@ -128,22 +108,6 @@ function handleAddStream() {
       </template>
 
       <template #afterActions="{ record }">
-        <AButton
-          v-if="record.enabled"
-          type="link"
-          size="small"
-          @click="disable(record.name)"
-        >
-          {{ $gettext('Disable') }}
-        </AButton>
-        <AButton
-          v-else
-          type="link"
-          size="small"
-          @click="enable(record.name)"
-        >
-          {{ $gettext('Enable') }}
-        </AButton>
         <AButton
           type="link"
           size="small"

+ 65 - 24
app/src/views/stream/columns.tsx

@@ -1,10 +1,12 @@
 import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
+import type { SiteStatus } from '@/api/site'
+import type { Stream } from '@/api/stream'
 import type { JSXElements } from '@/types'
 import { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
-import { Badge } from 'ant-design-vue'
 import env_group from '@/api/env_group'
-import { ConfigStatus } from '@/constants'
+import ProxyTargets from '@/components/ProxyTargets'
 import envGroupColumns from '@/views/environments/group/columns'
+import StreamStatusSelect from '@/views/stream/components/StreamStatusSelect.vue'
 
 const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
@@ -16,6 +18,28 @@ const columns: StdTableColumn[] = [{
   },
   search: true,
   width: 150,
+  customRender: ({ text }: CustomRenderArgs<Stream>) => {
+    const template: JSXElements = []
+
+    // Add stream name
+    template.push(
+      <div style="margin-bottom: 8px;">{text}</div>,
+    )
+
+    return h('div', {}, template)
+  },
+}, {
+  title: () => $gettext('Proxy Targets'),
+  dataIndex: 'proxy_targets',
+  width: 200,
+  customRender: ({ record }: CustomRenderArgs<Stream>) => {
+    if (record.proxy_targets && record.proxy_targets.length > 0) {
+      return h(ProxyTargets, {
+        targets: record.proxy_targets,
+      })
+    }
+    return h('span', '-')
+  },
 }, {
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
@@ -30,40 +54,57 @@ const columns: StdTableColumn[] = [{
       selectionType: 'radio',
     },
   },
+  batchEdit: true,
+  sorter: true,
+  pure: true,
+  width: 100,
+}, {
+  title: () => $gettext('Updated at'),
+  dataIndex: 'modified_at',
+  customRender: datetimeRender,
   sorter: true,
   pure: true,
   width: 150,
 }, {
   title: () => $gettext('Status'),
   dataIndex: 'status',
-  customRender: (args: CustomRenderArgs) => {
-    const template: JSXElements = []
-    const { text } = args
-    if (text === ConfigStatus.Enabled) {
-      template.push(<Badge status="success" />)
-      template.push(h('span', $gettext('Enabled')))
-    }
-    else if (text === ConfigStatus.Disabled) {
-      template.push(<Badge status="warning" />)
-      template.push(h('span', $gettext('Disabled')))
-    }
-
-    return h('div', template)
+  customRender: (args: CustomRenderArgs<Stream>) => {
+    const { record } = args
+    return h(StreamStatusSelect, {
+      'status': record.status,
+      'streamName': record.name,
+      'onStatusChanged': ({ status }: { status: SiteStatus }) => {
+        record.status = status
+      },
+      'onUpdate:status': (val?: SiteStatus) => {
+        // This will be handled by the component internal events
+        record.status = val!
+      },
+    })
+  },
+  search: {
+    type: 'select',
+    select: {
+      options: [
+        {
+          label: $gettext('Enabled'),
+          value: 'enabled',
+        },
+        {
+          label: $gettext('Disabled'),
+          value: 'disabled',
+        },
+      ],
+    },
   },
   sorter: true,
   pure: true,
-  width: 200,
-}, {
-  title: () => $gettext('Updated at'),
-  dataIndex: 'modified_at',
-  customRender: datetimeRender,
-  sorter: true,
-  pure: true,
-  width: 200,
+  width: 100,
+  fixed: 'right',
 }, {
   title: () => $gettext('Actions'),
   dataIndex: 'actions',
-  width: 250,
+  width: 80,
   fixed: 'right',
 }]
 

+ 13 - 10
app/src/views/stream/components/RightPanel/Basic.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import type { CheckedType } from '@/types'
+import type { SiteStatus } from '@/api/site'
 import { InfoCircleOutlined } from '@ant-design/icons-vue'
 import { StdSelector } from '@uozi-admin/curd'
 import { message, Modal } from 'ant-design-vue'
@@ -7,15 +7,17 @@ import { storeToRefs } from 'pinia'
 import envGroup from '@/api/env_group'
 import stream from '@/api/stream'
 import NodeSelector from '@/components/NodeSelector'
+import { ConfigStatus } from '@/constants'
 import { formatDateTime } from '@/lib/helper'
 import { useSettingsStore } from '@/pinia'
 import envGroupColumns from '@/views/environments/group/columns'
 import { useStreamEditorStore } from '../../store'
 import ConfigName from '../ConfigName.vue'
+import StreamStatusSelect from '../StreamStatusSelect.vue'
 
 const settings = useSettingsStore()
 const store = useStreamEditorStore()
-const { name, enabled, data } = storeToRefs(store)
+const { name, status, data } = storeToRefs(store)
 
 const [modal, ContextHolder] = Modal.useModal()
 const showSync = computed(() => !settings.is_remote)
@@ -23,7 +25,7 @@ const showSync = computed(() => !settings.is_remote)
 function enable() {
   stream.enable(name.value).then(() => {
     message.success($gettext('Enabled successfully'))
-    enabled.value = true
+    status.value = ConfigStatus.Enabled
   }).catch(r => {
     message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
   })
@@ -32,21 +34,21 @@ function enable() {
 function disable() {
   stream.disable(name.value).then(() => {
     message.success($gettext('Disabled successfully'))
-    enabled.value = false
+    status.value = ConfigStatus.Disabled
   }).catch(r => {
     message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
   })
 }
 
-function onChangeEnabled(checked: CheckedType) {
+function onChangeEnabled({ status }: { status: SiteStatus }) {
   modal.confirm({
-    title: checked ? $gettext('Do you want to enable this stream?') : $gettext('Do you want to disable this stream?'),
+    title: status === ConfigStatus.Enabled ? $gettext('Do you want to enable this stream?') : $gettext('Do you want to disable this stream?'),
     mask: false,
     centered: true,
     okText: $gettext('OK'),
     cancelText: $gettext('Cancel'),
     async onOk() {
-      if (checked)
+      if (status === ConfigStatus.Enabled)
         enable()
       else
         disable()
@@ -60,9 +62,10 @@ function onChangeEnabled(checked: CheckedType) {
     <ContextHolder />
 
     <AFormItem :label="$gettext('Enabled')">
-      <ASwitch
-        :checked="enabled"
-        @change="onChangeEnabled"
+      <StreamStatusSelect
+        v-model:status="status"
+        :stream-name="name"
+        @status-changed="onChangeEnabled"
       />
     </AFormItem>
 

+ 4 - 3
app/src/views/stream/components/StreamEditor.vue

@@ -4,12 +4,13 @@ import CodeEditor from '@/components/CodeEditor'
 import ConfigHistory from '@/components/ConfigHistory'
 import FooterToolBar from '@/components/FooterToolbar'
 import NgxConfigEditor from '@/components/NgxConfigEditor'
+import { ConfigStatus } from '@/constants'
 import { useStreamEditorStore } from '../store'
 
 const router = useRouter()
 
 const store = useStreamEditorStore()
-const { name, enabled, configText, filepath, saving, parseErrorStatus, parseErrorMessage, advanceMode } = storeToRefs(store)
+const { name, status, configText, filepath, saving, parseErrorStatus, parseErrorMessage, advanceMode } = storeToRefs(store)
 const showHistory = ref(false)
 </script>
 
@@ -18,7 +19,7 @@ const showHistory = ref(false)
     <template #title>
       <span style="margin-right: 10px">{{ $gettext('Edit %{n}', { n: name }) }}</span>
       <ATag
-        v-if="enabled"
+        v-if="status === ConfigStatus.Enabled"
         color="blue"
       >
         {{ $gettext('Enabled') }}
@@ -88,7 +89,7 @@ const showHistory = ref(false)
         class="domain-edit-container"
       >
         <NgxConfigEditor
-          :enabled="enabled"
+          :enabled="status === ConfigStatus.Enabled"
           context="stream"
         />
       </div>

+ 103 - 0
app/src/views/stream/components/StreamStatusSelect.vue

@@ -0,0 +1,103 @@
+<script setup lang="ts">
+import type { SiteStatus } from '@/api/site'
+import type { CheckedType } from '@/types'
+import { message, Modal } from 'ant-design-vue'
+import stream from '@/api/stream'
+import { ConfigStatus } from '@/constants'
+
+// Define props with TypeScript
+const props = defineProps<{
+  streamName: string
+}>()
+
+// Define event for status change notification
+const emit = defineEmits<{
+  statusChanged: [{ status: SiteStatus }]
+}>()
+
+// Use defineModel for v-model binding
+const status = defineModel<SiteStatus>('status')
+
+const [modal, ContextHolder] = Modal.useModal()
+
+// Enable the stream
+function enable() {
+  stream.enable(props.streamName).then(() => {
+    message.success($gettext('Enabled successfully'))
+    status.value = ConfigStatus.Enabled
+    emit('statusChanged', {
+      status: ConfigStatus.Enabled,
+    })
+  }).catch(r => {
+    message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
+  })
+}
+
+// Disable the stream
+function disable() {
+  stream.disable(props.streamName).then(() => {
+    message.success($gettext('Disabled successfully'))
+    status.value = ConfigStatus.Disabled
+    emit('statusChanged', {
+      status: ConfigStatus.Disabled,
+    })
+  }).catch(r => {
+    message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
+  })
+}
+
+function onChangeStatus(checked: CheckedType) {
+  const isChecked = checked === true || checked === 'true'
+  // Save original status to restore if user cancels
+  const originalStatus = status.value
+
+  const action = isChecked ? $gettext('enable') : $gettext('disable')
+
+  modal.confirm({
+    title: $gettext('Do you want to %{action} this stream?', { action }),
+    mask: false,
+    centered: true,
+    okText: $gettext('OK'),
+    cancelText: $gettext('Cancel'),
+    async onOk() {
+      if (isChecked) {
+        enable()
+      }
+      else {
+        disable()
+      }
+    },
+    onCancel() {
+      // Restore original status if user cancels
+      status.value = originalStatus
+    },
+  })
+}
+</script>
+
+<template>
+  <div class="stream-status-select">
+    <ContextHolder />
+    <div class="status-display">
+      <ASwitch
+        :checked="status === 'enabled'"
+        :checked-children="$gettext('Enabled')"
+        :un-checked-children="$gettext('Disabled')"
+        @change="onChangeStatus"
+      />
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.stream-status-select {
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+}
+
+.status-display {
+  display: flex;
+  align-items: center;
+}
+</style>

+ 4 - 3
app/src/views/stream/store.ts

@@ -6,6 +6,7 @@ import config from '@/api/config'
 import ngx from '@/api/ngx'
 import stream from '@/api/stream'
 import { useNgxConfigStore } from '@/components/NgxConfigEditor'
+import { ConfigStatus } from '@/constants'
 
 export const useStreamEditorStore = defineStore('streamEditor', () => {
   const name = ref('')
@@ -20,7 +21,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
   const certInfoMap = ref({}) as Ref<Record<number, CertificateInfo[]>>
   const filename = ref('')
   const filepath = ref('')
-  const enabled = ref(false)
+  const status = ref(ConfigStatus.Disabled)
 
   const ngxConfigStore = useNgxConfigStore()
   const { ngxConfig, configText, curServerIdx, curServer, curServerDirectives, curDirectivesMap } = storeToRefs(ngxConfigStore)
@@ -90,7 +91,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
     if (r.advanced)
       advanceMode.value = true
 
-    enabled.value = r.enabled
+    status.value = r.status
     parseErrorStatus.value = false
     parseErrorMessage.value = ''
     filename.value = r.name
@@ -152,7 +153,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
     filename,
     filepath,
     configText,
-    enabled,
+    status,
     init,
     save,
     handleModeChange,

+ 7 - 7
internal/cache/index.go

@@ -160,8 +160,8 @@ func (s *Scanner) Initialize(ctx context.Context) error {
 	configDir := filepath.Dir(nginx.GetConfPath())
 	availableDir := nginx.GetConfPath("sites-available")
 	enabledDir := nginx.GetConfPath("sites-enabled")
-	streamAvailableDir := nginx.GetConfPath("stream-available")
-	streamEnabledDir := nginx.GetConfPath("stream-enabled")
+	streamAvailableDir := nginx.GetConfPath("streams-available")
+	streamEnabledDir := nginx.GetConfPath("streams-enabled")
 
 	// Watch the main directories
 	err = s.watcher.Add(configDir)
@@ -184,18 +184,18 @@ func (s *Scanner) Initialize(ctx context.Context) error {
 		}
 	}
 
-	// Watch stream-available and stream-enabled if they exist
+	// Watch streams-available and streams-enabled if they exist
 	if _, err := os.Stat(streamAvailableDir); err == nil {
 		err = s.watcher.Add(streamAvailableDir)
 		if err != nil {
-			logger.Error("Failed to watch stream-available directory:", err)
+			logger.Error("Failed to watch streams-available directory:", err)
 		}
 	}
 
 	if _, err := os.Stat(streamEnabledDir); err == nil {
 		err = s.watcher.Add(streamEnabledDir)
 		if err != nil {
-			logger.Error("Failed to watch stream-enabled directory:", err)
+			logger.Error("Failed to watch streams-enabled directory:", err)
 		}
 	}
 
@@ -434,8 +434,8 @@ func (s *Scanner) ScanAllConfigs() error {
 		}
 	}
 
-	// Scan stream-available directory if it exists
-	streamAvailablePath := nginx.GetConfPath("stream-available", "")
+	// Scan streams-available directory if it exists
+	streamAvailablePath := nginx.GetConfPath("streams-available", "")
 	streamAvailableFiles, err := os.ReadDir(streamAvailablePath)
 	if err == nil {
 		for _, file := range streamAvailableFiles {

+ 5 - 0
internal/config/config.go

@@ -3,6 +3,7 @@ package config
 import (
 	"time"
 
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
 )
@@ -15,6 +16,9 @@ const (
 	StatusMaintenance ConfigStatus = "maintenance"
 )
 
+// ProxyTarget is an alias for upstream.ProxyTarget
+type ProxyTarget = upstream.ProxyTarget
+
 type Config struct {
 	Name            string                         `json:"name"`
 	Content         string                         `json:"content"`
@@ -28,6 +32,7 @@ type Config struct {
 	Status          ConfigStatus                   `json:"status"`
 	Dir             string                         `json:"dir"`
 	Urls            []string                       `json:"urls,omitempty"`
+	ProxyTargets    []ProxyTarget                  `json:"proxy_targets,omitempty"`
 	SyncNodeIds     []uint64                       `json:"sync_node_ids,omitempty"`
 	SyncOverwrite   bool                           `json:"sync_overwrite"`
 }

+ 57 - 0
internal/helper/debouncer.go

@@ -0,0 +1,57 @@
+package helper
+
+import (
+	"sync"
+	"time"
+)
+
+// Debouncer handles debounced execution of functions
+type Debouncer struct {
+	timer    *time.Timer
+	mutex    sync.Mutex
+	duration time.Duration
+	isFirst  bool
+}
+
+// NewDebouncer creates a new debouncer with the specified duration
+func NewDebouncer(duration time.Duration) *Debouncer {
+	return &Debouncer{
+		duration: duration,
+		isFirst:  true,
+	}
+}
+
+// Trigger executes the callback function with debouncing logic
+// For the first call, it executes immediately
+// For subsequent calls, it debounces with the configured duration
+func (d *Debouncer) Trigger(callback func()) {
+	d.mutex.Lock()
+	defer d.mutex.Unlock()
+
+	if d.isFirst {
+		d.isFirst = false
+		go callback() // Execute immediately for first call
+		return
+	}
+
+	// Stop existing timer if any
+	if d.timer != nil {
+		d.timer.Stop()
+	}
+
+	// Set new timer for debounced execution
+	d.timer = time.AfterFunc(d.duration, func() {
+		go callback()
+	})
+}
+
+// Stop cancels any pending debounced execution
+func (d *Debouncer) Stop() {
+	d.mutex.Lock()
+	defer d.mutex.Unlock()
+
+	if d.timer != nil {
+		d.timer.Stop()
+		d.timer = nil
+	}
+}

+ 101 - 0
internal/helper/debouncer_test.go

@@ -0,0 +1,101 @@
+package helper
+
+import (
+	"sync"
+	"testing"
+	"time"
+)
+
+func TestDebouncer_FirstCallImmediate(t *testing.T) {
+	debouncer := NewDebouncer(100 * time.Millisecond)
+
+	var called bool
+	var mu sync.Mutex
+
+	callback := func() {
+		mu.Lock()
+		called = true
+		mu.Unlock()
+	}
+
+	debouncer.Trigger(callback)
+
+	// Wait a short time for the goroutine to execute
+	time.Sleep(10 * time.Millisecond)
+
+	mu.Lock()
+	if !called {
+		t.Error("First call should execute immediately")
+	}
+	mu.Unlock()
+
+	debouncer.Stop()
+}
+
+func TestDebouncer_SubsequentCallsDebounced(t *testing.T) {
+	debouncer := NewDebouncer(50 * time.Millisecond)
+
+	var callCount int
+	var mu sync.Mutex
+
+	callback := func() {
+		mu.Lock()
+		callCount++
+		mu.Unlock()
+	}
+
+	// First call - should execute immediately
+	debouncer.Trigger(callback)
+	time.Sleep(10 * time.Millisecond)
+
+	// Multiple rapid calls - should be debounced
+	debouncer.Trigger(callback)
+	debouncer.Trigger(callback)
+	debouncer.Trigger(callback)
+
+	// Wait for debounce period
+	time.Sleep(70 * time.Millisecond)
+
+	mu.Lock()
+	if callCount != 2 { // First immediate + one debounced
+		t.Errorf("Expected 2 calls, got %d", callCount)
+	}
+	mu.Unlock()
+
+	debouncer.Stop()
+}
+
+func TestDebouncer_Stop(t *testing.T) {
+	debouncer := NewDebouncer(100 * time.Millisecond)
+
+	var called bool
+	var mu sync.Mutex
+
+	callback := func() {
+		mu.Lock()
+		called = true
+		mu.Unlock()
+	}
+
+	// First call to set isFirst to false
+	debouncer.Trigger(callback)
+	time.Sleep(10 * time.Millisecond)
+
+	// Reset called flag
+	mu.Lock()
+	called = false
+	mu.Unlock()
+
+	// Trigger and immediately stop
+	debouncer.Trigger(callback)
+	debouncer.Stop()
+
+	// Wait longer than debounce period
+	time.Sleep(150 * time.Millisecond)
+
+	mu.Lock()
+	if called {
+		t.Error("Callback should not be called after Stop()")
+	}
+	mu.Unlock()
+}

+ 14 - 8
internal/site/index.go

@@ -8,12 +8,14 @@ import (
 	"strings"
 
 	"github.com/0xJacky/Nginx-UI/internal/cache"
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 )
 
 type SiteIndex struct {
-	Path    string
-	Content string
-	Urls    []string
+	Path         string
+	Content      string
+	Urls         []string
+	ProxyTargets []ProxyTarget
 }
 
 var (
@@ -41,9 +43,10 @@ func scanForSite(configPath string, content []byte) error {
 	serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
 
 	siteIndex := SiteIndex{
-		Path:    configPath,
-		Content: string(content),
-		Urls:    []string{},
+		Path:         configPath,
+		Content:      string(content),
+		Urls:         []string{},
+		ProxyTargets: []ProxyTarget{},
 	}
 
 	// Map to track hosts, their SSL status and port
@@ -153,8 +156,11 @@ func scanForSite(configPath string, content []byte) error {
 		siteIndex.Urls = append(siteIndex.Urls, url)
 	}
 
-	// Only store if we found valid URLs
-	if len(siteIndex.Urls) > 0 {
+	// Parse proxy targets from the configuration content
+	siteIndex.ProxyTargets = upstream.ParseProxyTargetsFromRawContent(string(content))
+
+	// Only store if we found valid URLs or proxy targets
+	if len(siteIndex.Urls) > 0 || len(siteIndex.ProxyTargets) > 0 {
 		IndexedSites[filepath.Base(configPath)] = &siteIndex
 	}
 

+ 5 - 0
internal/site/type.go

@@ -5,6 +5,7 @@ import (
 
 	"github.com/0xJacky/Nginx-UI/internal/cert"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
 )
@@ -17,6 +18,9 @@ const (
 	SiteStatusMaintenance SiteStatus = "maintenance"
 )
 
+// ProxyTarget is an alias for upstream.ProxyTarget
+type ProxyTarget = upstream.ProxyTarget
+
 type Site struct {
 	*model.Site
 	Name            string                         `json:"name"`
@@ -28,4 +32,5 @@ type Site struct {
 	Tokenized       *nginx.NgxConfig               `json:"tokenized,omitempty"`
 	CertInfo        map[int][]*cert.Info           `json:"cert_info,omitempty"`
 	Filepath        string                         `json:"filepath"`
+	ProxyTargets    []ProxyTarget                  `json:"proxy_targets,omitempty"`
 }

+ 58 - 0
internal/stream/index.go

@@ -0,0 +1,58 @@
+package stream
+
+import (
+	"path/filepath"
+	"strings"
+
+	"github.com/0xJacky/Nginx-UI/internal/cache"
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
+)
+
+type StreamIndex struct {
+	Path         string
+	Content      string
+	ProxyTargets []upstream.ProxyTarget
+}
+
+var (
+	IndexedStreams = make(map[string]*StreamIndex)
+)
+
+func GetIndexedStream(path string) *StreamIndex {
+	if stream, ok := IndexedStreams[path]; ok {
+		return stream
+	}
+	return &StreamIndex{}
+}
+
+func init() {
+	cache.RegisterCallback(scanForStream)
+}
+
+func scanForStream(configPath string, content []byte) error {
+	// Only process stream configuration files
+	if !isStreamConfig(configPath) {
+		return nil
+	}
+
+	streamIndex := StreamIndex{
+		Path:         configPath,
+		Content:      string(content),
+		ProxyTargets: []upstream.ProxyTarget{},
+	}
+
+	// Parse proxy targets from the configuration content
+	streamIndex.ProxyTargets = upstream.ParseProxyTargetsFromRawContent(string(content))
+	// Only store if we found proxy targets
+	if len(streamIndex.ProxyTargets) > 0 {
+		IndexedStreams[filepath.Base(configPath)] = &streamIndex
+	}
+
+	return nil
+}
+
+// isStreamConfig checks if the config path is a stream configuration
+func isStreamConfig(configPath string) bool {
+	return strings.Contains(configPath, "streams-available") ||
+		strings.Contains(configPath, "streams-enabled")
+}

+ 79 - 0
internal/stream/index_test.go

@@ -0,0 +1,79 @@
+package stream
+
+import (
+	"testing"
+)
+
+func TestIsStreamConfig(t *testing.T) {
+	tests := []struct {
+		path     string
+		expected bool
+	}{
+		{"streams-available/test.conf", true},
+		{"streams-enabled/test.conf", true},
+		{"/etc/nginx/streams-available/test.conf", true},
+		{"/etc/nginx/streams-enabled/test.conf", true},
+		{"/var/lib/nginx/streams-available/my-stream.conf", true},
+		{"/home/user/nginx/streams-enabled/tcp-proxy.conf", true},
+		{"sites-available/test.conf", false},
+		{"sites-enabled/test.conf", false},
+		{"/etc/nginx/conf.d/test.conf", false},
+		{"test.conf", false},
+	}
+
+	for _, test := range tests {
+		result := isStreamConfig(test.path)
+		if result != test.expected {
+			t.Errorf("isStreamConfig(%q) = %v, expected %v", test.path, result, test.expected)
+		}
+	}
+}
+
+func TestScanForStream(t *testing.T) {
+	// Clear the IndexedStreams map
+	IndexedStreams = make(map[string]*StreamIndex)
+
+	config := `upstream my-tcp {
+    server 127.0.0.1:9000;
+}
+server {
+    listen 1234-1236;
+    resolver 8.8.8.8 valid=1s;
+    proxy_pass example.com:$server_port;
+}`
+
+	// Test with a valid stream config path
+	err := scanForStream("streams-available/test.conf", []byte(config))
+	if err != nil {
+		t.Errorf("scanForStream failed: %v", err)
+	}
+
+	// Check if the stream was indexed
+	if len(IndexedStreams) != 1 {
+		t.Errorf("Expected 1 indexed stream, got %d", len(IndexedStreams))
+	}
+
+	stream := IndexedStreams["test.conf"]
+	if stream == nil {
+		t.Fatal("Stream not found in index")
+	}
+
+	if len(stream.ProxyTargets) != 2 {
+		t.Errorf("Expected 2 proxy targets, got %d", len(stream.ProxyTargets))
+		for i, target := range stream.ProxyTargets {
+			t.Logf("Target %d: %+v", i, target)
+		}
+	}
+
+	// Test with a non-stream config path
+	IndexedStreams = make(map[string]*StreamIndex)
+	err = scanForStream("sites-available/test.conf", []byte(config))
+	if err != nil {
+		t.Errorf("scanForStream failed: %v", err)
+	}
+
+	// Should not be indexed
+	if len(IndexedStreams) != 0 {
+		t.Errorf("Expected 0 indexed streams for non-stream config, got %d", len(IndexedStreams))
+	}
+}

+ 275 - 0
internal/upstream/proxy_parser.go

@@ -0,0 +1,275 @@
+package upstream
+
+import (
+	"net/url"
+	"regexp"
+	"strings"
+
+	"github.com/0xJacky/Nginx-UI/internal/nginx"
+)
+
+// ProxyTarget represents a proxy destination
+type ProxyTarget struct {
+	Host string `json:"host"`
+	Port string `json:"port"`
+	Type string `json:"type"` // "proxy_pass" or "upstream"
+}
+
+// ParseProxyTargets extracts proxy targets from nginx configuration
+func ParseProxyTargets(config *nginx.NgxConfig) []ProxyTarget {
+	var targets []ProxyTarget
+
+	if config == nil {
+		return targets
+	}
+
+	// Parse upstream servers
+	for _, upstream := range config.Upstreams {
+		upstreamTargets := parseUpstreamServers(upstream)
+		targets = append(targets, upstreamTargets...)
+	}
+
+	// Parse proxy_pass directives in servers
+	for _, server := range config.Servers {
+		proxyTargets := parseServerProxyPass(server)
+		targets = append(targets, proxyTargets...)
+	}
+
+	return deduplicateTargets(targets)
+}
+
+// ParseProxyTargetsFromRawContent parses proxy targets from raw nginx configuration content
+func ParseProxyTargetsFromRawContent(content string) []ProxyTarget {
+	var targets []ProxyTarget
+
+	// First, collect all upstream names
+	upstreamNames := make(map[string]bool)
+	upstreamRegex := regexp.MustCompile(`(?s)upstream\s+([^\s]+)\s*\{([^}]+)\}`)
+	upstreamMatches := upstreamRegex.FindAllStringSubmatch(content, -1)
+
+	// Parse upstream blocks and collect upstream names
+	for _, match := range upstreamMatches {
+		if len(match) >= 3 {
+			upstreamName := match[1]
+			upstreamNames[upstreamName] = true
+
+			upstreamContent := match[2]
+			serverRegex := regexp.MustCompile(`(?m)^\s*server\s+([^;]+);`)
+			serverMatches := serverRegex.FindAllStringSubmatch(upstreamContent, -1)
+
+			for _, serverMatch := range serverMatches {
+				if len(serverMatch) >= 2 {
+					target := parseServerAddress(strings.TrimSpace(serverMatch[1]), "upstream")
+					if target.Host != "" {
+						targets = append(targets, target)
+					}
+				}
+			}
+		}
+	}
+
+	// Parse proxy_pass directives, but skip upstream references
+	proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
+	proxyMatches := proxyPassRegex.FindAllStringSubmatch(content, -1)
+
+	for _, match := range proxyMatches {
+		if len(match) >= 2 {
+			proxyPassURL := strings.TrimSpace(match[1])
+			// Skip if this proxy_pass references an upstream
+			if !isUpstreamReference(proxyPassURL, upstreamNames) {
+				target := parseProxyPassURL(proxyPassURL)
+				if target.Host != "" {
+					targets = append(targets, target)
+				}
+			}
+		}
+	}
+
+	return deduplicateTargets(targets)
+}
+
+// parseUpstreamServers extracts server addresses from upstream blocks
+func parseUpstreamServers(upstream *nginx.NgxUpstream) []ProxyTarget {
+	var targets []ProxyTarget
+
+	for _, directive := range upstream.Directives {
+		if directive.Directive == "server" {
+			target := parseServerAddress(directive.Params, "upstream")
+			if target.Host != "" {
+				targets = append(targets, target)
+			}
+		}
+	}
+
+	return targets
+}
+
+// parseServerProxyPass extracts proxy_pass targets from server blocks
+func parseServerProxyPass(server *nginx.NgxServer) []ProxyTarget {
+	var targets []ProxyTarget
+
+	// Check directives in server block
+	for _, directive := range server.Directives {
+		if directive.Directive == "proxy_pass" {
+			target := parseProxyPassURL(directive.Params)
+			if target.Host != "" {
+				targets = append(targets, target)
+			}
+		}
+	}
+
+	// Check directives in location blocks
+	for _, location := range server.Locations {
+		locationTargets := parseLocationProxyPass(location.Content)
+		targets = append(targets, locationTargets...)
+	}
+
+	return targets
+}
+
+// parseLocationProxyPass extracts proxy_pass from location content
+func parseLocationProxyPass(content string) []ProxyTarget {
+	var targets []ProxyTarget
+
+	// Use regex to find proxy_pass directives
+	proxyPassRegex := regexp.MustCompile(`(?m)^\s*proxy_pass\s+([^;]+);`)
+	matches := proxyPassRegex.FindAllStringSubmatch(content, -1)
+
+	for _, match := range matches {
+		if len(match) >= 2 {
+			target := parseProxyPassURL(strings.TrimSpace(match[1]))
+			if target.Host != "" {
+				targets = append(targets, target)
+			}
+		}
+	}
+
+	return targets
+}
+
+// parseProxyPassURL parses a proxy_pass URL and extracts host and port
+func parseProxyPassURL(proxyPass string) ProxyTarget {
+	proxyPass = strings.TrimSpace(proxyPass)
+
+	// Handle HTTP/HTTPS URLs (e.g., "http://backend")
+	if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
+		if parsedURL, err := url.Parse(proxyPass); err == nil {
+			host := parsedURL.Hostname()
+			port := parsedURL.Port()
+
+			// Set default ports if not specified
+			if port == "" {
+				if parsedURL.Scheme == "https" {
+					port = "443"
+				} else {
+					port = "80"
+				}
+			}
+
+			return ProxyTarget{
+				Host: host,
+				Port: port,
+				Type: "proxy_pass",
+			}
+		}
+	}
+
+	// Handle direct address format for stream module (e.g., "127.0.0.1:8080", "backend.example.com:12345")
+	// This is used in stream configurations where proxy_pass doesn't require a protocol
+	if !strings.Contains(proxyPass, "://") {
+		return parseServerAddress(proxyPass, "proxy_pass")
+	}
+
+	return ProxyTarget{}
+}
+
+// parseServerAddress parses upstream server address
+func parseServerAddress(serverAddr string, targetType string) ProxyTarget {
+	serverAddr = strings.TrimSpace(serverAddr)
+
+	// Remove additional parameters (weight, max_fails, etc.)
+	parts := strings.Fields(serverAddr)
+	if len(parts) == 0 {
+		return ProxyTarget{}
+	}
+
+	addr := parts[0]
+
+	// Handle IPv6 addresses
+	if strings.HasPrefix(addr, "[") {
+		// IPv6 format: [::1]:8080
+		if idx := strings.LastIndex(addr, "]:"); idx != -1 {
+			host := addr[1:idx]
+			port := addr[idx+2:]
+			return ProxyTarget{
+				Host: host,
+				Port: port,
+				Type: targetType,
+			}
+		}
+		// IPv6 without port: [::1]
+		host := strings.Trim(addr, "[]")
+		return ProxyTarget{
+			Host: host,
+			Port: "80",
+			Type: targetType,
+		}
+	}
+
+	// Handle IPv4 addresses and hostnames
+	if strings.Contains(addr, ":") {
+		parts := strings.Split(addr, ":")
+		if len(parts) == 2 {
+			return ProxyTarget{
+				Host: parts[0],
+				Port: parts[1],
+				Type: targetType,
+			}
+		}
+	}
+
+	// No port specified, use default
+	return ProxyTarget{
+		Host: addr,
+		Port: "80",
+		Type: targetType,
+	}
+}
+
+// deduplicateTargets removes duplicate proxy targets
+func deduplicateTargets(targets []ProxyTarget) []ProxyTarget {
+	seen := make(map[string]bool)
+	var result []ProxyTarget
+
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		if !seen[key] {
+			seen[key] = true
+			result = append(result, target)
+		}
+	}
+
+	return result
+}
+
+// isUpstreamReference checks if a proxy_pass URL references an upstream block
+func isUpstreamReference(proxyPass string, upstreamNames map[string]bool) bool {
+	proxyPass = strings.TrimSpace(proxyPass)
+
+	// For HTTP/HTTPS URLs, parse the URL to extract the hostname
+	if strings.HasPrefix(proxyPass, "http://") || strings.HasPrefix(proxyPass, "https://") {
+		if parsedURL, err := url.Parse(proxyPass); err == nil {
+			hostname := parsedURL.Hostname()
+			// Check if the hostname matches any upstream name
+			return upstreamNames[hostname]
+		}
+	}
+
+	// For stream module, proxy_pass can directly reference upstream name without protocol
+	// Check if the proxy_pass value directly matches an upstream name
+	if !strings.Contains(proxyPass, "://") && !strings.Contains(proxyPass, ":") {
+		return upstreamNames[proxyPass]
+	}
+
+	return false
+}

+ 381 - 0
internal/upstream/proxy_parser_test.go

@@ -0,0 +1,381 @@
+package upstream
+
+import (
+	"testing"
+)
+
+func TestParseProxyTargetsFromRawContent(t *testing.T) {
+	config := `map $http_upgrade $connection_upgrade {
+    default upgrade;
+    '' close;
+}
+upstream api-1 {
+    server 127.0.0.1:9000;
+    server 127.0.0.1:443;
+}
+upstream api-2 {
+    server 127.0.0.1:9003;
+    server 127.0.0.1:9005;
+}
+server {
+    listen 80;
+    listen [::]:80;
+    server_name test.jackyu.cn;
+    location / {
+        # First attempt to serve request as file, then
+        # as directory, then fall back to displaying a 404.
+        index index.html;
+        try_files $uri $uri/ /index.html;
+    }
+    location /admin {
+        index admin.html;
+        try_files $uri $uri/ /admin.html;
+    }
+    location /user {
+        index user.html;
+        try_files $uri $uri/ /user.html;
+    }
+    location /api/ {
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+        proxy_pass http://api-1/;
+        proxy_redirect off;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        client_max_body_size 1000m;
+    }
+}
+server {
+    listen 443 ssl;
+    listen [::]:443 ssl;
+    server_name test.jackyu.cn;
+    ssl_certificate /etc/nginx/ssl/test.jackyu.cn_P256/fullchain.cer;
+    ssl_certificate_key /etc/nginx/ssl/test.jackyu.cn_P256/private.key;
+    root /var/www/ibeta/html;
+    index index.html;
+    http2 on;
+    access_log /var/log/nginx/test.jackyu.cn.log main;
+    location / {
+        # First attempt to serve request as file, then
+        # as directory, then fall back to displaying a 404.
+        index index.html;
+        try_files $uri $uri/ /index.html;
+    }
+    location /admin {
+        index admin.html;
+        try_files $uri $uri/ /admin.html;
+    }
+    location /user {
+        index user.html;
+        try_files $uri $uri/ /user.html;
+    }
+    location /api/ {
+        proxy_http_version 1.1;
+        proxy_set_header Upgrade $http_upgrade;
+        proxy_set_header Connection $connection_upgrade;
+        proxy_pass http://api-1/;
+        proxy_redirect off;
+        proxy_set_header Host $host;
+        proxy_set_header X-Real-IP $remote_addr;
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header X-Forwarded-Proto $scheme;
+        client_max_body_size 100m;
+    }
+}`
+
+	targets := ParseProxyTargetsFromRawContent(config)
+
+	// Expected targets: 4 upstream servers (2 from api-1, 2 from api-2)
+	// proxy_pass http://api-1/ should be ignored since it references an upstream
+	expectedTargets := []ProxyTarget{
+		{Host: "127.0.0.1", Port: "9000", Type: "upstream"},
+		{Host: "127.0.0.1", Port: "443", Type: "upstream"},
+		{Host: "127.0.0.1", Port: "9003", Type: "upstream"},
+		{Host: "127.0.0.1", Port: "9005", Type: "upstream"},
+	}
+
+	if len(targets) != len(expectedTargets) {
+		t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
+		for i, target := range targets {
+			t.Logf("Target %d: %+v", i, target)
+		}
+		return
+	}
+
+	// Create a map for easier comparison
+	targetMap := make(map[string]ProxyTarget)
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		targetMap[key] = target
+	}
+
+	for _, expected := range expectedTargets {
+		key := expected.Host + ":" + expected.Port + ":" + expected.Type
+		if _, found := targetMap[key]; !found {
+			t.Errorf("Expected target not found: %+v", expected)
+		}
+	}
+}
+
+func TestIsUpstreamReference(t *testing.T) {
+	upstreamNames := map[string]bool{
+		"api-1":   true,
+		"api-2":   true,
+		"backend": true,
+	}
+
+	tests := []struct {
+		proxyPass string
+		expected  bool
+	}{
+		{"http://api-1/", true},
+		{"http://api-1", true},
+		{"https://api-2/path", true},
+		{"http://backend", true},
+		{"http://127.0.0.1:8080", false},
+		{"https://example.com", false},
+		{"http://unknown-upstream", false},
+	}
+
+	for _, test := range tests {
+		result := isUpstreamReference(test.proxyPass, upstreamNames)
+		if result != test.expected {
+			t.Errorf("isUpstreamReference(%q) = %v, expected %v", test.proxyPass, result, test.expected)
+		}
+	}
+}
+
+func TestParseProxyTargetsWithDirectProxyPass(t *testing.T) {
+	config := `upstream api-1 {
+    server 127.0.0.1:9000;
+    server 127.0.0.1:443;
+}
+server {
+    listen 80;
+    server_name test.jackyu.cn;
+    location /api/ {
+        proxy_pass http://api-1/;
+    }
+    location /external/ {
+        proxy_pass http://external.example.com:8080/;
+    }
+    location /another/ {
+        proxy_pass https://another.example.com/;
+    }
+}`
+
+	targets := ParseProxyTargetsFromRawContent(config)
+
+	// Expected targets:
+	// - 2 upstream servers from api-1
+	// - 2 direct proxy_pass targets (external.example.com:8080, another.example.com:443)
+	// - proxy_pass http://api-1/ should be ignored since it references an upstream
+	expectedTargets := []ProxyTarget{
+		{Host: "127.0.0.1", Port: "9000", Type: "upstream"},
+		{Host: "127.0.0.1", Port: "443", Type: "upstream"},
+		{Host: "external.example.com", Port: "8080", Type: "proxy_pass"},
+		{Host: "another.example.com", Port: "443", Type: "proxy_pass"},
+	}
+
+	if len(targets) != len(expectedTargets) {
+		t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
+		for i, target := range targets {
+			t.Logf("Target %d: %+v", i, target)
+		}
+		return
+	}
+
+	// Create a map for easier comparison
+	targetMap := make(map[string]ProxyTarget)
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		targetMap[key] = target
+	}
+
+	for _, expected := range expectedTargets {
+		key := expected.Host + ":" + expected.Port + ":" + expected.Type
+		if _, found := targetMap[key]; !found {
+			t.Errorf("Expected target not found: %+v", expected)
+		}
+	}
+}
+
+func TestParseProxyTargetsFromStreamConfig(t *testing.T) {
+	config := `upstream backend {
+    server 127.0.0.1:9000;
+    server 127.0.0.1:9001;
+}
+
+server {
+    listen 12345;
+    proxy_pass backend;
+}
+
+server {
+    listen 12346;
+    proxy_pass 192.168.1.100:8080;
+}
+
+server {
+    listen 12347;
+    proxy_pass example.com:3306;
+}`
+
+	targets := ParseProxyTargetsFromRawContent(config)
+
+	// Expected targets:
+	// - 2 upstream servers from backend
+	// - 2 direct proxy_pass targets (192.168.1.100:8080, example.com:3306)
+	// - proxy_pass backend should be ignored since it references an upstream
+	expectedTargets := []ProxyTarget{
+		{Host: "127.0.0.1", Port: "9000", Type: "upstream"},
+		{Host: "127.0.0.1", Port: "9001", Type: "upstream"},
+		{Host: "192.168.1.100", Port: "8080", Type: "proxy_pass"},
+		{Host: "example.com", Port: "3306", Type: "proxy_pass"},
+	}
+
+	if len(targets) != len(expectedTargets) {
+		t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
+		for i, target := range targets {
+			t.Logf("Target %d: %+v", i, target)
+		}
+		return
+	}
+
+	// Create a map for easier comparison
+	targetMap := make(map[string]ProxyTarget)
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		targetMap[key] = target
+	}
+
+	for _, expected := range expectedTargets {
+		key := expected.Host + ":" + expected.Port + ":" + expected.Type
+		if _, found := targetMap[key]; !found {
+			t.Errorf("Expected target not found: %+v", expected)
+		}
+	}
+}
+
+func TestParseProxyTargetsFromMixedConfig(t *testing.T) {
+	config := `upstream web_backend {
+    server web1.example.com:80;
+    server web2.example.com:80;
+}
+
+upstream stream_backend {
+    server stream1.example.com:12345;
+    server stream2.example.com:12345;
+}
+
+# HTTP server block
+server {
+    listen 80;
+    server_name example.com;
+    location / {
+        proxy_pass http://web_backend/;
+    }
+    location /api {
+        proxy_pass http://api.example.com:8080/;
+    }
+}
+
+# Stream server blocks
+server {
+    listen 12345;
+    proxy_pass stream_backend;
+}
+
+server {
+    listen 3306;
+    proxy_pass mysql.example.com:3306;
+}`
+
+	targets := ParseProxyTargetsFromRawContent(config)
+
+	// Expected targets:
+	// - 2 upstream servers from web_backend
+	// - 2 upstream servers from stream_backend
+	// - 1 direct HTTP proxy_pass (api.example.com:8080)
+	// - 1 direct stream proxy_pass (mysql.example.com:3306)
+	// - proxy_pass http://web_backend/ and proxy_pass stream_backend should be ignored
+	expectedTargets := []ProxyTarget{
+		{Host: "web1.example.com", Port: "80", Type: "upstream"},
+		{Host: "web2.example.com", Port: "80", Type: "upstream"},
+		{Host: "stream1.example.com", Port: "12345", Type: "upstream"},
+		{Host: "stream2.example.com", Port: "12345", Type: "upstream"},
+		{Host: "api.example.com", Port: "8080", Type: "proxy_pass"},
+		{Host: "mysql.example.com", Port: "3306", Type: "proxy_pass"},
+	}
+
+	if len(targets) != len(expectedTargets) {
+		t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
+		for i, target := range targets {
+			t.Logf("Target %d: %+v", i, target)
+		}
+		return
+	}
+
+	// Create a map for easier comparison
+	targetMap := make(map[string]ProxyTarget)
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		targetMap[key] = target
+	}
+
+	for _, expected := range expectedTargets {
+		key := expected.Host + ":" + expected.Port + ":" + expected.Type
+		if _, found := targetMap[key]; !found {
+			t.Errorf("Expected target not found: %+v", expected)
+		}
+	}
+}
+
+func TestParseProxyTargetsFromUserConfig(t *testing.T) {
+	config := `upstream my-tcp {
+    server 127.0.0.1:9000;
+}
+server {
+    listen 1234-1236;
+    resolver 8.8.8.8 valid=1s;
+    proxy_pass example.com:$server_port;
+}`
+
+	targets := ParseProxyTargetsFromRawContent(config)
+
+	// Print actual results for debugging
+	t.Logf("Found %d targets:", len(targets))
+	for i, target := range targets {
+		t.Logf("Target %d: Host=%s, Port=%s, Type=%s", i+1, target.Host, target.Port, target.Type)
+	}
+
+	// Expected targets:
+	// - 1 upstream server from my-tcp
+	// - 1 proxy_pass target (example.com with variable port should still be parsed)
+	expectedTargets := []ProxyTarget{
+		{Host: "127.0.0.1", Port: "9000", Type: "upstream"},
+		{Host: "example.com", Port: "$server_port", Type: "proxy_pass"},
+	}
+
+	if len(targets) != len(expectedTargets) {
+		t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
+		return
+	}
+
+	// Create a map for easier comparison
+	targetMap := make(map[string]ProxyTarget)
+	for _, target := range targets {
+		key := target.Host + ":" + target.Port + ":" + target.Type
+		targetMap[key] = target
+	}
+
+	for _, expected := range expectedTargets {
+		key := expected.Host + ":" + expected.Port + ":" + expected.Type
+		if _, found := targetMap[key]; !found {
+			t.Errorf("Expected target not found: %+v", expected)
+		}
+	}
+}