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
 // Request Body: AutoBackup model with required fields
 // Response: Created auto backup configuration
 // Response: Created auto backup configuration
 func CreateAutoBackup(c *gin.Context) {
 func CreateAutoBackup(c *gin.Context) {
-	ctx := cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
+	cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
 		"name":                 "required",
 		"name":                 "required",
 		"backup_type":          "required",
 		"backup_type":          "required",
 		"storage_type":         "required",
 		"storage_type":         "required",
@@ -54,16 +54,15 @@ func CreateAutoBackup(c *gin.Context) {
 			ctx.AbortWithError(err)
 			ctx.AbortWithError(err)
 			return
 			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.
 // 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
 // Request Body: Partial AutoBackup model with fields to update
 // Response: Updated auto backup configuration
 // Response: Updated auto backup configuration
 func ModifyAutoBackup(c *gin.Context) {
 func ModifyAutoBackup(c *gin.Context) {
-	ctx := cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
+	cosy.Core[model.AutoBackup](c).SetValidRules(gin.H{
 		"name":                 "omitempty",
 		"name":                 "omitempty",
 		"backup_type":          "omitempty",
 		"backup_type":          "omitempty",
 		"storage_type":         "omitempty",
 		"storage_type":         "omitempty",
@@ -104,20 +103,20 @@ func ModifyAutoBackup(c *gin.Context) {
 			ctx.AbortWithError(err)
 			ctx.AbortWithError(err)
 			return
 			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.
 // 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())
 		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{
 		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 {
 type Stream struct {
 	ModifiedAt      time.Time                      `json:"modified_at"`
 	ModifiedAt      time.Time                      `json:"modified_at"`
 	Advanced        bool                           `json:"advanced"`
 	Advanced        bool                           `json:"advanced"`
-	Enabled         bool                           `json:"enabled"`
+	Status          config.ConfigStatus            `json:"status"`
 	Name            string                         `json:"name"`
 	Name            string                         `json:"name"`
 	Config          string                         `json:"config"`
 	Config          string                         `json:"config"`
 	ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
 	ChatGPTMessages []openai.ChatCompletionMessage `json:"chatgpt_messages,omitempty"`
@@ -32,6 +32,7 @@ type Stream struct {
 	EnvGroupID      uint64                         `json:"env_group_id"`
 	EnvGroupID      uint64                         `json:"env_group_id"`
 	EnvGroup        *model.EnvGroup                `json:"env_group,omitempty"`
 	EnvGroup        *model.EnvGroup                `json:"env_group,omitempty"`
 	SyncNodeIDs     []uint64                       `json:"sync_node_ids" gorm:"serializer:json"`
 	SyncNodeIDs     []uint64                       `json:"sync_node_ids" gorm:"serializer:json"`
+	ProxyTargets    []config.ProxyTarget           `json:"proxy_targets,omitempty"`
 }
 }
 
 
 func GetStreams(c *gin.Context) {
 func GetStreams(c *gin.Context) {
@@ -130,15 +131,29 @@ func GetStreams(c *gin.Context) {
 			continue
 			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
 		// Add the config to the result list after passing all filters
 		configs = append(configs, config.Config{
 		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
 	// Check if the stream is enabled
-	enabled := true
+	status := config.StatusEnabled
 	if _, err := os.Stat(nginx.GetConfPath("streams-enabled", name)); os.IsNotExist(err) {
 	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
 	// Retrieve or create ChatGPT log for this stream
@@ -201,7 +216,7 @@ func GetStream(c *gin.Context) {
 		c.JSON(http.StatusOK, Stream{
 		c.JSON(http.StatusOK, Stream{
 			ModifiedAt:      file.ModTime(),
 			ModifiedAt:      file.ModTime(),
 			Advanced:        streamModel.Advanced,
 			Advanced:        streamModel.Advanced,
-			Enabled:         enabled,
+			Status:          status,
 			Name:            name,
 			Name:            name,
 			Config:          string(origContent),
 			Config:          string(origContent),
 			ChatGPTMessages: chatgpt.Content,
 			ChatGPTMessages: chatgpt.Content,
@@ -223,7 +238,7 @@ func GetStream(c *gin.Context) {
 	c.JSON(http.StatusOK, Stream{
 	c.JSON(http.StatusOK, Stream{
 		ModifiedAt:      file.ModTime(),
 		ModifiedAt:      file.ModTime(),
 		Advanced:        streamModel.Advanced,
 		Advanced:        streamModel.Advanced,
-		Enabled:         enabled,
+		Status:          status,
 		Name:            name,
 		Name:            name,
 		Config:          nginxConfig.FmtCode(),
 		Config:          nginxConfig.FmtCode(),
 		Tokenized:       nginxConfig,
 		Tokenized:       nginxConfig,

+ 149 - 14
api/upstream/upstream.go

@@ -1,15 +1,23 @@
 package upstream
 package upstream
 
 
 import (
 import (
+	"context"
+	"net/http"
+	"sync"
+	"time"
+
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/gin-gonic/gin"
 	"github.com/gin-gonic/gin"
 	"github.com/gorilla/websocket"
 	"github.com/gorilla/websocket"
 	"github.com/uozi-tech/cosy/logger"
 	"github.com/uozi-tech/cosy/logger"
-	"net/http"
-	"time"
 )
 )
 
 
+type wsMessage struct {
+	data interface{}
+	done chan error
+}
+
 func AvailabilityTest(c *gin.Context) {
 func AvailabilityTest(c *gin.Context) {
 	var upGrader = websocket.Upgrader{
 	var upGrader = websocket.Upgrader{
 		CheckOrigin: func(r *http.Request) bool {
 		CheckOrigin: func(r *http.Request) bool {
@@ -25,24 +33,151 @@ func AvailabilityTest(c *gin.Context) {
 
 
 	defer ws.Close()
 	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']
     PortScannerPortScanner: typeof import('./src/components/PortScanner/PortScanner.vue')['default']
     PortScannerPortScannerCompact: typeof import('./src/components/PortScanner/PortScannerCompact.vue')['default']
     PortScannerPortScannerCompact: typeof import('./src/components/PortScanner/PortScannerCompact.vue')['default']
     ProcessingStatusProcessingStatus: typeof import('./src/components/ProcessingStatus/ProcessingStatus.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']
     ReactiveFromNowReactiveFromNow: typeof import('./src/components/ReactiveFromNow/ReactiveFromNow.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     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 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 {
 export interface Site extends ModelBase {
   modified_at: string
   modified_at: string
   path: string
   path: string
@@ -23,6 +29,7 @@ export interface Site extends ModelBase {
   env_group?: EnvGroup
   env_group?: EnvGroup
   sync_node_ids: number[]
   sync_node_ids: number[]
   urls?: string[]
   urls?: string[]
+  proxy_targets?: ProxyTarget[]
   status: SiteStatus
   status: SiteStatus
 }
 }
 
 

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

@@ -1,12 +1,13 @@
 import type { EnvGroup } from './env_group'
 import type { EnvGroup } from './env_group'
 import type { NgxConfig } from '@/api/ngx'
 import type { NgxConfig } from '@/api/ngx'
 import type { ChatComplicationMessage } from '@/api/openai'
 import type { ChatComplicationMessage } from '@/api/openai'
+import type { ProxyTarget, SiteStatus } from '@/api/site'
 import { extendCurdApi, http, useCurdApi } from '@uozi-admin/request'
 import { extendCurdApi, http, useCurdApi } from '@uozi-admin/request'
 
 
 export interface Stream {
 export interface Stream {
   modified_at: string
   modified_at: string
   advanced: boolean
   advanced: boolean
-  enabled: boolean
+  status: SiteStatus
   name: string
   name: string
   filepath: string
   filepath: string
   config: string
   config: string
@@ -15,6 +16,7 @@ export interface Stream {
   env_group_id: number
   env_group_id: number
   env_group?: EnvGroup
   env_group?: EnvGroup
   sync_node_ids: number[]
   sync_node_ids: number[]
+  proxy_targets?: ProxyTarget[]
 }
 }
 
 
 const baseUrl = '/streams'
 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "الإجراءات"
 msgstr "الإجراءات"
@@ -161,7 +161,7 @@ msgstr "النسبة الفعلية للعامل إلى المُهيأ"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "إضافة"
 msgstr "إضافة"
 
 
@@ -188,11 +188,11 @@ msgstr "أضف مكان"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "أضف موقع"
 msgstr "أضف موقع"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "أضف Stream"
 msgstr "أضف Stream"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "تمت الإضافة بنجاح"
 msgstr "تمت الإضافة بنجاح"
 
 
@@ -276,7 +276,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "هل أنت متأكد أنك تريد الحذف نهائيًا؟"
 msgstr "هل أنت متأكد أنك تريد الحذف نهائيًا؟"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "هل أنت متأكد أنك تريد الحذف؟"
 msgstr "هل أنت متأكد أنك تريد الحذف؟"
 
 
@@ -567,6 +567,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "إلغاء"
 msgstr "إلغاء"
 
 
@@ -1203,7 +1204,7 @@ msgstr "حدد اسم منطقة الذاكرة المشتركة والحجم،
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "حذف"
 msgstr "حذف"
 
 
@@ -1251,7 +1252,7 @@ msgstr "فشل حذف الدفق %{name} من %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "تم حذف الدفق %{name} من %{node} بنجاح"
 msgstr "تم حذف الدفق %{name} من %{node} بنجاح"
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "حذف البث: ‎%{stream_name}"
 msgstr "حذف البث: ‎%{stream_name}"
 
 
@@ -1325,13 +1326,10 @@ msgid "Directory path to store cache files"
 msgstr "مسار الدليل لتخزين ملفات الذاكرة المؤقتة"
 msgstr "مسار الدليل لتخزين ملفات الذاكرة المؤقتة"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "تعطيل"
 msgstr "تعطيل"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "تعطيل"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "فشل تعطيل التجديد التلقائي لـ %{name}"
 msgstr "فشل تعطيل التجديد التلقائي لـ %{name}"
@@ -1392,15 +1390,16 @@ msgstr "تم تعطيل الدفق %{name} من %{node} بنجاح"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "معطل"
 msgstr "معطل"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "تم التعطيل بنجاح"
 msgstr "تم التعطيل بنجاح"
 
 
@@ -1430,6 +1429,10 @@ msgstr "لا تقم بتمكين هذا الخيار إلا إذا كنت متأ
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "هل تريد %{action} هذا الموقع؟"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "هل تريد تعطيل التجديد التلقائي للشهادة؟"
 msgstr "هل تريد تعطيل التجديد التلقائي للشهادة؟"
@@ -1505,7 +1508,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "مكرر"
 msgstr "مكرر"
 
 
@@ -1554,13 +1557,10 @@ msgid "Email (*)"
 msgstr "البريد الإلكتروني (*)"
 msgstr "البريد الإلكتروني (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "تمكين"
 msgstr "تمكين"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "تفعيل"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "تم تفعيل المصادقة الثنائية بنجاح"
 msgstr "تم تفعيل المصادقة الثنائية بنجاح"
@@ -1659,9 +1659,10 @@ msgstr "تفعيل TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "مفعل"
 msgstr "مفعل"
@@ -1669,7 +1670,7 @@ msgstr "مفعل"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "تم التفعيل بنجاح"
 msgstr "تم التفعيل بنجاح"
 
 
@@ -1906,7 +1907,7 @@ msgstr "فشل حذف الشهادة من قاعدة البيانات: %{error}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "فشل في تعطيل %{msg}"
 msgstr "فشل في تعطيل %{msg}"
 
 
@@ -1916,7 +1917,7 @@ msgstr "فشل تعطيل وضع الصيانة %{msg}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "فشل في التفعيل %{msg}"
 msgstr "فشل في التفعيل %{msg}"
 
 
@@ -2655,7 +2656,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "صيانة"
 msgstr "صيانة"
 
 
@@ -2684,7 +2685,7 @@ msgstr "إدارة التكوينات"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "إدارة المواقع"
 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"
 msgid "Manage Streams"
 msgstr "إدارة التدفقات"
 msgstr "إدارة التدفقات"
 
 
@@ -2858,12 +2859,12 @@ msgstr "توجيه متعدد الأسطر"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "اسم"
 msgstr "اسم"
 
 
@@ -3124,7 +3125,7 @@ msgstr "يتضمن Nginx.conf دليل streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "لا"
 msgstr "لا"
 
 
@@ -3146,7 +3147,7 @@ msgid "Node"
 msgstr "العقدة"
 msgstr "العقدة"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "مجموعة العقد"
 msgstr "مجموعة العقد"
@@ -3259,6 +3260,7 @@ msgstr "إيقاف"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3276,7 +3278,8 @@ msgstr "غير متصل"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "حسنًا"
 msgstr "حسنًا"
 
 
@@ -3667,6 +3670,15 @@ msgstr "مزود"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "وكيل"
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "رقم الأمن العام"
 msgstr "رقم الأمن العام"
@@ -4527,7 +4539,7 @@ msgstr "ثابت"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "الحالة"
 msgstr "الحالة"
 
 
@@ -5099,6 +5111,7 @@ msgstr "الثلاثاء"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "يتطلب المصادقة الثنائية"
 msgstr "يتطلب المصادقة الثنائية"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5135,7 +5148,7 @@ msgstr "تم التحديث بنجاح"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5169,6 +5182,10 @@ msgstr "تحميل الملفات"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "تحميل المجلدات"
 msgstr "تحميل المجلدات"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "أعلى التيار"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "اسم المنبع"
 msgstr "اسم المنبع"
@@ -5454,6 +5471,12 @@ msgstr "رموزك القديمة لن تعمل بعد الآن."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "مفاتيح المرور الخاصة بك"
 msgstr "مفاتيح المرور الخاصة بك"
 
 
+#~ msgid "Disable"
+#~ msgstr "تعطيل"
+
+#~ msgid "Enable"
+#~ msgstr "تفعيل"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "خطأ آخر نسخة احتياطية"
 #~ msgstr "خطأ آخر نسخة احتياطية"
 
 
@@ -5653,9 +5676,6 @@ msgstr "مفاتيح المرور الخاصة بك"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "فشل مزامنة التكوين %{config_name} إلى %{env_name}، الاستجابة: %{resp}"
 #~ msgstr "فشل مزامنة التكوين %{config_name} إلى %{env_name}، الاستجابة: %{resp}"
 
 
-#~ msgid "Target"
-#~ msgstr "الهدف"
-
 #~ msgid ""
 #~ msgid ""
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "2FA."
 #~ "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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Aktionen"
 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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Hinzufügen"
 msgstr "Hinzufügen"
 
 
@@ -188,11 +188,11 @@ msgstr "Ort hinzufügen"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Seite hinzufügen"
 msgstr "Seite hinzufügen"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Stream hinzufügen"
 msgstr "Stream hinzufügen"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Erfolgreich hinzugefügt"
 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?"
 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/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?"
 msgid "Are you sure you want to delete?"
 msgstr "Sind Sie sicher, dass Sie löschen möchten?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Abbrechen"
 msgstr "Abbrechen"
 
 
@@ -1218,7 +1219,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Löschen"
 msgstr "Löschen"
 
 
@@ -1266,7 +1267,7 @@ msgstr "Löschen des Streams %{name} von %{node} fehlgeschlagen"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Stream %{name} wurde erfolgreich von %{node} gelöscht"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Stream löschen: %{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"
 msgstr "Verzeichnispfad zum Speichern der Cache-Dateien"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Deaktivieren"
 msgstr "Deaktivieren"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Deaktivieren"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Automatische Verlängerung für %{name} konnte nicht deaktiviert werden"
 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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Deaktiviert"
 msgstr "Deaktiviert"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Erfolgreich deaktiviert"
 msgstr "Erfolgreich deaktiviert"
 
 
@@ -1447,6 +1446,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "Möchten Sie diese Website %{action}?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Möchtest du die automatische Zertifikatsverlängerung deaktivieren?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Duplizieren"
 msgstr "Duplizieren"
 
 
@@ -1574,13 +1577,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 msgstr "Email (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "aktivieren"
 msgstr "aktivieren"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Aktivieren"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA erfolgreich aktiviert"
 msgstr "2FA erfolgreich aktiviert"
@@ -1679,9 +1679,10 @@ msgstr "TOTP aktivieren"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Aktiviert"
 msgstr "Aktiviert"
@@ -1689,7 +1690,7 @@ msgstr "Aktiviert"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Erfolgreich aktiviert"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Deaktivierung von %{msg} fehlgeschlagen"
 msgstr "Deaktivierung von %{msg} fehlgeschlagen"
 
 
@@ -1936,7 +1937,7 @@ msgstr "Deaktivierung des Wartungsmodus fehlgeschlagen: %{msg}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Aktiviern von %{msg} fehlgeschlagen"
 msgstr "Aktiviern von %{msg} fehlgeschlagen"
 
 
@@ -2682,7 +2683,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Wartung"
 msgstr "Wartung"
 
 
@@ -2712,7 +2713,7 @@ msgstr "Verwalte Konfigurationen"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Verwalte Seiten"
 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"
 msgid "Manage Streams"
 msgstr "Streams verwalten"
 msgstr "Streams verwalten"
 
 
@@ -2886,12 +2887,12 @@ msgstr "Mehrzeilige Direktive"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Name"
 msgstr "Name"
 
 
@@ -3154,7 +3155,7 @@ msgstr "Nginx.conf enthält das streams-enabled-Verzeichnis"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Nein"
 msgstr "Nein"
 
 
@@ -3176,7 +3177,7 @@ msgid "Node"
 msgstr "Node"
 msgstr "Node"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Node-Gruppe"
 msgstr "Node-Gruppe"
@@ -3292,6 +3293,7 @@ msgstr "Aus"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3309,7 +3311,8 @@ msgstr "Offline"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "OK"
 msgstr "OK"
 
 
@@ -3715,6 +3718,15 @@ msgstr "Anbieter"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Öffentliche Sicherheitsnummer"
 msgstr "Öffentliche Sicherheitsnummer"
@@ -4589,7 +4601,7 @@ msgstr "Statisch"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Status"
 msgstr "Status"
 
 
@@ -5174,6 +5186,7 @@ msgstr "Dienstag"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 msgstr "Zwei-Faktor-Authentifizierung erforderlich"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5210,7 +5223,7 @@ msgstr "Erfolgreich aktualisiert"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5244,6 +5257,10 @@ msgstr "Dateien hochladen"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Ordner hochladen"
 msgstr "Ordner hochladen"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Upstream"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Upstream-Name"
 msgstr "Upstream-Name"
@@ -5543,6 +5560,12 @@ msgstr "Ihre alten Codes funktionieren nicht mehr."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Deine Passkeys"
 msgstr "Deine Passkeys"
 
 
+#~ msgid "Disable"
+#~ msgstr "Deaktivieren"
+
+#~ msgid "Enable"
+#~ msgstr "Aktivieren"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Letzter Sicherungsfehler"
 #~ msgstr "Letzter Sicherungsfehler"
 
 
@@ -5749,9 +5772,6 @@ msgstr "Deine Passkeys"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "Speichern erfolgreich"
 #~ msgstr "Speichern erfolgreich"
 
 
-#~ msgid "Target"
-#~ msgstr "Ziel"
-
 #~ msgid "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 #~ msgid "Bist du sicher, dass du diese Richtlinie löschen möchtest?"
 #~ msgstr "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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr ""
 msgstr ""
@@ -145,7 +145,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr ""
 msgstr ""
 
 
@@ -172,11 +172,11 @@ msgstr ""
 msgid "Add Site"
 msgid "Add Site"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr ""
 msgstr ""
 
 
@@ -260,7 +260,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr ""
 msgstr ""
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr ""
 msgstr ""
 
 
@@ -549,6 +549,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr ""
 msgstr ""
 
 
@@ -1144,7 +1145,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr ""
 msgstr ""
 
 
@@ -1192,7 +1193,7 @@ msgstr ""
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
@@ -1266,13 +1267,10 @@ msgid "Directory path to store cache files"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr ""
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr ""
 msgstr ""
@@ -1333,15 +1331,16 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1371,6 +1370,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr ""
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr ""
 msgstr ""
@@ -1445,7 +1448,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr ""
 msgstr ""
 
 
@@ -1494,13 +1497,10 @@ msgid "Email (*)"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr ""
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr ""
 msgstr ""
@@ -1599,9 +1599,10 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr ""
 msgstr ""
@@ -1609,7 +1610,7 @@ msgstr ""
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1846,7 +1847,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr ""
 msgstr ""
 
 
@@ -1856,7 +1857,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr ""
 msgstr ""
 
 
@@ -2576,7 +2577,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr ""
 msgstr ""
 
 
@@ -2603,7 +2604,7 @@ msgstr ""
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr ""
 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"
 msgid "Manage Streams"
 msgstr ""
 msgstr ""
 
 
@@ -2777,12 +2778,12 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr ""
 msgstr ""
 
 
@@ -3043,7 +3044,7 @@ msgstr ""
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr ""
 msgstr ""
 
 
@@ -3065,7 +3066,7 @@ msgid "Node"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr ""
 msgstr ""
@@ -3174,6 +3175,7 @@ msgstr ""
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3191,7 +3193,8 @@ msgstr ""
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr ""
 msgstr ""
 
 
@@ -3576,6 +3579,14 @@ msgstr ""
 msgid "Proxy"
 msgid "Proxy"
 msgstr ""
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr ""
 msgstr ""
@@ -4424,7 +4435,7 @@ msgstr ""
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr ""
 msgstr ""
 
 
@@ -4947,6 +4958,7 @@ msgstr ""
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -4983,7 +4995,7 @@ msgstr ""
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5017,6 +5029,10 @@ msgstr ""
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr ""
 msgstr ""
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr ""
 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Acciones"
 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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Agregar"
 msgstr "Agregar"
 
 
@@ -195,11 +195,11 @@ msgstr "Agregar Ubicación"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Agregar Sitio"
 msgstr "Agregar Sitio"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Agregar Stream"
 msgstr "Agregar Stream"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Agregado exitoso"
 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?"
 msgstr "¿Estás seguro de que quieres eliminar permanentemente?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "¿Está seguro de que quiere borrar?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Cancelar"
 msgstr "Cancelar"
 
 
@@ -1229,7 +1230,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Eliminar"
 msgstr "Eliminar"
 
 
@@ -1277,7 +1278,7 @@ msgstr "Error al eliminar el flujo %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Se eliminó el flujo %{name} de %{node} correctamente"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar stream: %{site_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é"
 msgstr "Ruta del directorio para almacenar archivos de caché"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Desactivar"
 msgstr "Desactivar"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Desactivar"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Error al desactivar la renovación automática para %{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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Desactivado"
 msgstr "Desactivado"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Desactivado con éxito"
 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?"
 msgid "Do you want to %{action} this site?"
 msgstr "¿Desea %{action} este sitio?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "¿Desea deshabilitar la renovación automática de certificado?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Duplicar"
 msgstr "Duplicar"
 
 
@@ -1583,13 +1586,10 @@ msgid "Email (*)"
 msgstr "Correo (*)"
 msgstr "Correo (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "habilitar"
 msgstr "habilitar"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Habilitar"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Habilitar 2FA exitoso"
 msgstr "Habilitar 2FA exitoso"
@@ -1688,9 +1688,10 @@ msgstr "Habilitar TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Habilitado"
 msgstr "Habilitado"
@@ -1698,7 +1699,7 @@ msgstr "Habilitado"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Habilitado con éxito"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Error al deshabilitar %{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/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Error al habilitar %{msg}"
 msgstr "Error al habilitar %{msg}"
 
 
@@ -2691,7 +2692,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Mantenimiento"
 msgstr "Mantenimiento"
 
 
@@ -2720,7 +2721,7 @@ msgstr "Administrar configuraciones"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Administrar sitios"
 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"
 msgid "Manage Streams"
 msgstr "Administrar Transmisiones"
 msgstr "Administrar Transmisiones"
 
 
@@ -2894,12 +2895,12 @@ msgstr "Directiva multilínea"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Nombre"
 msgstr "Nombre"
 
 
@@ -3162,7 +3163,7 @@ msgstr "Nginx.conf incluye el directorio streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "No"
 msgstr "No"
 
 
@@ -3184,7 +3185,7 @@ msgid "Node"
 msgstr "Nodo"
 msgstr "Nodo"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Grupo de nodos"
 msgstr "Grupo de nodos"
@@ -3300,6 +3301,7 @@ msgstr "Apagado"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3317,7 +3319,8 @@ msgstr "Desconectado"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "OK"
 msgstr "OK"
 
 
@@ -3726,6 +3729,15 @@ msgstr "Proveedor"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Número de Seguridad Pública"
 msgstr "Número de Seguridad Pública"
@@ -4598,7 +4610,7 @@ msgstr "Estático"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Estado"
 msgstr "Estado"
 
 
@@ -5182,6 +5194,7 @@ msgstr "Martes"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Se requiere autenticación de dos factores"
 msgstr "Se requiere autenticación de dos factores"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: 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/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5252,6 +5265,10 @@ msgstr "Subir archivos"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Subir carpetas"
 msgstr "Subir carpetas"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Aguas arriba"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Nombre de la Transmisión"
 msgstr "Nombre de la Transmisión"
@@ -5546,6 +5563,12 @@ msgstr "Tus códigos antiguos ya no funcionarán."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Sus llaves de acceso"
 msgstr "Sus llaves de acceso"
 
 
+#~ msgid "Disable"
+#~ msgstr "Desactivar"
+
+#~ msgid "Enable"
+#~ msgstr "Habilitar"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Último error de copia de seguridad"
 #~ 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, "
 #~ "Sincronización de la configuración %{config_name} a %{env_name} fallida, "
 #~ "respuesta: %{resp}"
 #~ "respuesta: %{resp}"
 
 
-#~ msgid "Target"
-#~ msgstr "Objetivo"
-
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "¿No puede escanear? Utilice la vinculación con una llave de texto"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Ajouter"
 msgstr "Ajouter"
 
 
@@ -193,11 +193,11 @@ msgstr "Ajouter une localisation"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Ajouter un site"
 msgstr "Ajouter un site"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Ajouter un flux"
 msgstr "Ajouter un flux"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Ajouté avec succès"
 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 ?"
 msgstr "Êtes-vous sûr de vouloir supprimer définitivement ?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Etes-vous sûr que vous voulez supprimer ?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Annuler"
 msgstr "Annuler"
 
 
@@ -1225,7 +1226,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Supprimer"
 msgstr "Supprimer"
 
 
@@ -1273,7 +1274,7 @@ msgstr "Échec de la suppression du flux %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Le flux %{name} a été supprimé de %{node} avec succès"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Supprimer le flux : %{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"
 msgstr "Chemin du répertoire pour stocker les fichiers de cache"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Désactiver"
 msgstr "Désactiver"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Désactiver"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Échec de la désactivation du renouvellement automatique pour %{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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Désactivé"
 msgstr "Désactivé"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Désactivé avec succès"
 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?"
 msgid "Do you want to %{action} this site?"
 msgstr "Voulez-vous %{action} ce 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Voulez-vous désactiver le renouvellement automatique des certificats ?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Dupliquer"
 msgstr "Dupliquer"
 
 
@@ -1577,13 +1580,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 msgstr "Email (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "activer"
 msgstr "activer"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Activer"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA activé avec succès"
 msgstr "2FA activé avec succès"
@@ -1682,9 +1682,10 @@ msgstr "Activer TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Activé"
 msgstr "Activé"
@@ -1692,7 +1693,7 @@ msgstr "Activé"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Activé avec succès"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Impossible de désactiver %{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/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Impossible d'activer %{msg}"
 msgstr "Impossible d'activer %{msg}"
 
 
@@ -2689,7 +2690,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Maintenance"
 msgstr "Maintenance"
 
 
@@ -2718,7 +2719,7 @@ msgstr "Gérer les configurations"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Gérer les 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"
 msgid "Manage Streams"
 msgstr "Gérer les flux"
 msgstr "Gérer les flux"
 
 
@@ -2892,12 +2893,12 @@ msgstr "Directive multiligne"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Nom"
 msgstr "Nom"
 
 
@@ -3160,7 +3161,7 @@ msgstr "Nginx.conf inclut le répertoire streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Non"
 msgstr "Non"
 
 
@@ -3182,7 +3183,7 @@ msgid "Node"
 msgstr "Nœud"
 msgstr "Nœud"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Groupe de nœuds"
 msgstr "Groupe de nœuds"
@@ -3297,6 +3298,7 @@ msgstr "Désactivé"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3314,7 +3316,8 @@ msgstr "Hors ligne"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "OK"
 msgstr "OK"
 
 
@@ -3723,6 +3726,15 @@ msgstr "Fournisseur"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Numéro de sécurité publique"
 msgstr "Numéro de sécurité publique"
@@ -4597,7 +4609,7 @@ msgstr "Statique"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Statut"
 msgstr "Statut"
 
 
@@ -5190,6 +5202,7 @@ msgstr "Mardi"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Authentification à deux facteurs requise"
 msgstr "Authentification à deux facteurs requise"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: 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/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5260,6 +5273,10 @@ msgstr "Téléverser des fichiers"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Télécharger des dossiers"
 msgstr "Télécharger des dossiers"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Amont"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Nom de l'amont"
 msgstr "Nom de l'amont"
@@ -5557,6 +5574,12 @@ msgstr "Vos anciens codes ne fonctionneront plus."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Vos clés d'accès"
 msgstr "Vos clés d'accès"
 
 
+#~ msgid "Disable"
+#~ msgstr "Désactiver"
+
+#~ msgid "Enable"
+#~ msgstr "Activer"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Dernière erreur de sauvegarde"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "操作"
 msgstr "操作"
@@ -162,7 +162,7 @@ msgstr "実際のワーカー数と設定値の比率"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "追加"
 msgstr "追加"
 
 
@@ -189,11 +189,11 @@ msgstr "Locationを追加"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "サイトを追加"
 msgstr "サイトを追加"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Streamを追加"
 msgstr "Streamを追加"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "正常に追加されました"
 msgstr "正常に追加されました"
 
 
@@ -277,7 +277,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "完全に削除してもよろしいですか?"
 msgstr "完全に削除してもよろしいですか?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "削除してもよろしいですか?"
 msgstr "削除してもよろしいですか?"
 
 
@@ -568,6 +568,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "キャンセル"
 msgstr "キャンセル"
 
 
@@ -1183,7 +1184,7 @@ msgstr "共有メモリゾーンの名前とサイズを定義します(例: p
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "削除"
 msgstr "削除"
 
 
@@ -1231,7 +1232,7 @@ msgstr "%{node} からのストリーム %{name} の削除に失敗しました"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "ストリーム %{name} を %{node} から削除しました"
 msgstr "ストリーム %{name} を %{node} から削除しました"
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "ストリームを削除: %{stream_name}"
 msgstr "ストリームを削除: %{stream_name}"
 
 
@@ -1305,13 +1306,10 @@ msgid "Directory path to store cache files"
 msgstr "キャッシュファイルを保存するディレクトリパス"
 msgstr "キャッシュファイルを保存するディレクトリパス"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "無効化"
 msgstr "無効化"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "無効化"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name} の自動更新の無効化に失敗しました"
 msgstr "%{name} の自動更新の無効化に失敗しました"
@@ -1372,15 +1370,16 @@ msgstr "ストリーム %{name} を %{node} から無効化しました"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "無効"
 msgstr "無効"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "無効化に成功しました"
 msgstr "無効化に成功しました"
 
 
@@ -1410,6 +1409,10 @@ msgstr "このオプションは必要な場合以外は有効にしないでく
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "このサイトを%{action}しますか?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "自動証明書更新を無効にしますか?"
 msgstr "自動証明書更新を無効にしますか?"
@@ -1483,7 +1486,7 @@ msgstr "一部のブラウザのセキュリティポリシーのため、localh
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "複製"
 msgstr "複製"
 
 
@@ -1532,13 +1535,10 @@ msgid "Email (*)"
 msgstr "メールアドレス (*)"
 msgstr "メールアドレス (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "有効にする"
 msgstr "有効にする"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "有効にする"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FAが有効化されました"
 msgstr "2FAが有効化されました"
@@ -1637,9 +1637,10 @@ msgstr "TOTP を有効にする"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "有効"
 msgstr "有効"
@@ -1647,7 +1648,7 @@ msgstr "有効"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "有効化に成功しました"
 msgstr "有効化に成功しました"
 
 
@@ -1884,7 +1885,7 @@ msgstr "データベースから証明書の削除に失敗しました: %{error
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "%{msg}の無効化に失敗しました"
 msgstr "%{msg}の無効化に失敗しました"
 
 
@@ -1894,7 +1895,7 @@ msgstr "メンテナンスモードの無効化に失敗しました: %{msg}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "有効化に失敗しました %{msg}"
 msgstr "有効化に失敗しました %{msg}"
 
 
@@ -2620,7 +2621,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "メンテナンス"
 msgstr "メンテナンス"
 
 
@@ -2649,7 +2650,7 @@ msgstr "設定管理"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "サイト管理"
 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"
 msgid "Manage Streams"
 msgstr "ストリームの管理"
 msgstr "ストリームの管理"
 
 
@@ -2823,12 +2824,12 @@ msgstr "複数行ディレクティブ"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "名前"
 msgstr "名前"
 
 
@@ -3089,7 +3090,7 @@ msgstr "Nginx.conf には streams-enabled ディレクトリが含まれてい
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "いいえ"
 msgstr "いいえ"
 
 
@@ -3111,7 +3112,7 @@ msgid "Node"
 msgstr "ノード"
 msgstr "ノード"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "ノードグループ"
 msgstr "ノードグループ"
@@ -3220,6 +3221,7 @@ msgstr "オフ"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3237,7 +3239,8 @@ msgstr "オフライン"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "OK"
 msgstr "OK"
 
 
@@ -3625,6 +3628,15 @@ msgstr "プロバイダー"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "プロキシ"
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "公安番号"
 msgstr "公安番号"
@@ -4477,7 +4489,7 @@ msgstr "静的"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "ステータス"
 msgstr "ステータス"
 
 
@@ -5016,6 +5028,7 @@ msgstr "火曜日"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "二要素認証が必要です"
 msgstr "二要素認証が必要です"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5052,7 +5065,7 @@ msgstr "更新に成功しました"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5086,6 +5099,10 @@ msgstr "ファイルをアップロード"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "フォルダをアップロード"
 msgstr "フォルダをアップロード"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "アップストリーム"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "アップストリーム名"
 msgstr "アップストリーム名"
@@ -5358,6 +5375,12 @@ msgstr "以前のコードはもう使えません。"
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "あなたのパスキー"
 msgstr "あなたのパスキー"
 
 
+#~ msgid "Disable"
+#~ msgstr "無効化"
+
+#~ msgid "Enable"
+#~ msgstr "有効にする"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "最後のバックアップエラー"
 #~ msgstr "最後のバックアップエラー"
 
 

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

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

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

@@ -131,8 +131,8 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr ""
 msgstr ""
@@ -151,7 +151,7 @@ msgstr ""
 #: src/language/curd.ts:19
 #: src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr ""
 msgstr ""
 
 
@@ -180,11 +180,11 @@ msgstr ""
 msgid "Add Site"
 msgid "Add Site"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr ""
 msgstr ""
 
 
@@ -269,7 +269,7 @@ msgstr ""
 
 
 #: src/language/curd.ts:25
 #: src/language/curd.ts:25
 #: src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr ""
 msgstr ""
 
 
@@ -559,6 +559,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr ""
 msgstr ""
 
 
@@ -1125,7 +1126,7 @@ msgstr ""
 #: src/language/curd.ts:9
 #: src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr ""
 msgstr ""
 
 
@@ -1175,7 +1176,7 @@ msgstr ""
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr ""
 msgstr ""
 
 
@@ -1250,13 +1251,10 @@ msgid "Directory path to store cache files"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr ""
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr ""
 msgstr ""
@@ -1319,16 +1317,17 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1358,6 +1357,10 @@ msgstr ""
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr ""
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr ""
 msgstr ""
@@ -1430,7 +1433,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr ""
 msgstr ""
 
 
@@ -1480,13 +1483,10 @@ msgid "Email (*)"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr ""
 msgstr ""
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr ""
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr ""
 msgstr ""
@@ -1587,10 +1587,11 @@ msgstr ""
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr ""
 msgstr ""
@@ -1598,7 +1599,7 @@ msgstr ""
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr ""
 msgstr ""
 
 
@@ -1836,7 +1837,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr ""
 msgstr ""
 
 
@@ -1846,7 +1847,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr ""
 msgstr ""
 
 
@@ -2548,7 +2549,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr ""
 msgstr ""
 
 
@@ -2577,7 +2578,7 @@ msgid "Manage Sites"
 msgstr ""
 msgstr ""
 
 
 #: src/routes/modules/streams.ts:10
 #: src/routes/modules/streams.ts:10
-#: src/views/stream/StreamList.vue:101
+#: src/views/stream/StreamList.vue:81
 msgid "Manage Streams"
 msgid "Manage Streams"
 msgstr ""
 msgstr ""
 
 
@@ -2754,12 +2755,12 @@ msgstr ""
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr ""
 msgstr ""
 
 
@@ -3024,7 +3025,7 @@ msgstr ""
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: src/views/preference/tabs/CertSettings.vue:73
 #: src/views/site/site_list/SiteList.vue:98
 #: src/views/site/site_list/SiteList.vue:98
-#: src/views/stream/StreamList.vue:155
+#: src/views/stream/StreamList.vue:119
 msgid "No"
 msgid "No"
 msgstr ""
 msgstr ""
 
 
@@ -3046,8 +3047,8 @@ msgid "Node"
 msgstr ""
 msgstr ""
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr ""
 msgstr ""
@@ -3152,6 +3153,7 @@ msgstr ""
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3170,7 +3172,8 @@ msgstr ""
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr ""
 msgstr ""
 
 
@@ -3545,6 +3548,15 @@ msgstr ""
 msgid "Proxy"
 msgid "Proxy"
 msgstr ""
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr ""
 msgstr ""
@@ -4389,8 +4401,8 @@ msgstr ""
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr ""
 msgstr ""
 
 
@@ -4866,6 +4878,7 @@ msgstr ""
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr ""
 msgstr ""
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -4903,8 +4916,8 @@ msgstr ""
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -4939,6 +4952,10 @@ msgstr ""
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr ""
 msgstr ""
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr ""
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr ""
 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Ações"
 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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Adicionar"
 msgstr "Adicionar"
 
 
@@ -189,11 +189,11 @@ msgstr "Adicionar Local"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Adicionar Site"
 msgstr "Adicionar Site"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Adicionar Stream"
 msgstr "Adicionar Stream"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Adicionado com sucesso"
 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?"
 msgstr "Tem a certeza de que pretende eliminar permanentemente?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Tem certeza que pretende eliminar?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Cancelar"
 msgstr "Cancelar"
 
 
@@ -1216,7 +1217,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Eliminar"
 msgstr "Eliminar"
 
 
@@ -1264,7 +1265,7 @@ msgstr "Falha ao eliminar o fluxo %{name} de %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "O fluxo %{name} foi eliminado de %{node} com sucesso"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Eliminar 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"
 msgstr "Caminho do diretório para armazenar ficheiros de cache"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Desativar"
 msgstr "Desativar"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Desativar"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Falha ao desativar a renovação automática para %{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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Desativado"
 msgstr "Desativado"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Desactivado com sucesso"
 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?"
 msgid "Do you want to %{action} this site?"
 msgstr "Deseja %{action} este 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Desactivar a renovação automática do certificado?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Duplicado"
 msgstr "Duplicado"
 
 
@@ -1568,13 +1571,10 @@ msgid "Email (*)"
 msgstr "E-mail (*)"
 msgstr "E-mail (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "ativar"
 msgstr "ativar"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Activar"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA Activado com Sucesso"
 msgstr "2FA Activado com Sucesso"
@@ -1673,9 +1673,10 @@ msgstr "Ativar TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Activado"
 msgstr "Activado"
@@ -1683,7 +1684,7 @@ msgstr "Activado"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Activado com sucesso"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Falha ao desactivar %{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/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Falha ao Activar %{msg}"
 msgstr "Falha ao Activar %{msg}"
 
 
@@ -2675,7 +2676,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Manutenção"
 msgstr "Manutenção"
 
 
@@ -2704,7 +2705,7 @@ msgstr "Gerir Configurações"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Gerir 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"
 msgid "Manage Streams"
 msgstr "Gerir Streams"
 msgstr "Gerir Streams"
 
 
@@ -2878,12 +2879,12 @@ msgstr "Diretiva Multilinha"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Nome"
 msgstr "Nome"
 
 
@@ -3146,7 +3147,7 @@ msgstr "Nginx.conf inclui o diretório streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Não"
 msgstr "Não"
 
 
@@ -3168,7 +3169,7 @@ msgid "Node"
 msgstr "Nó"
 msgstr "Nó"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Grupo de nós"
 msgstr "Grupo de nós"
@@ -3283,6 +3284,7 @@ msgstr "Desligado"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3300,7 +3302,8 @@ msgstr "Off-line"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "OK"
 msgstr "OK"
 
 
@@ -3704,6 +3707,15 @@ msgstr "Provedor"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Número de Segurança Pública"
 msgstr "Número de Segurança Pública"
@@ -4577,7 +4589,7 @@ msgstr "Estático"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Estado"
 msgstr "Estado"
 
 
@@ -5157,6 +5169,7 @@ msgstr "Terça-feira"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Autenticação de dois fatores necessária"
 msgstr "Autenticação de dois fatores necessária"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: 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/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5227,6 +5240,10 @@ msgstr "Carregar ficheiros"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Carregar pastas"
 msgstr "Carregar pastas"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "A montante"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Nome do Upstream"
 msgstr "Nome do Upstream"
@@ -5520,6 +5537,12 @@ msgstr "Os seus códigos antigos não funcionarão mais."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "As suas chaves de acesso"
 msgstr "As suas chaves de acesso"
 
 
+#~ msgid "Disable"
+#~ msgstr "Desativar"
+
+#~ msgid "Enable"
+#~ msgstr "Activar"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Último erro de backup"
 #~ 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, "
 #~ "Sincronização de configuração %{config_name} para %{env_name} falhou, "
 #~ "resposta: %{resp}"
 #~ "resposta: %{resp}"
 
 
-#~ msgid "Target"
-#~ msgstr "Destino"
-
 #~ msgid "The recovery code is only displayed once, please save it in a safe place."
 #~ msgid "The recovery code is only displayed once, please save it in a safe place."
 #~ msgstr ""
 #~ msgstr ""
 #~ "O código de recuperação é apresentado apenas uma vez, guarde-o num local "
 #~ "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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Действия"
 msgstr "Действия"
@@ -166,7 +166,7 @@ msgstr "Фактическое соотношение рабочих к наст
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Добавить"
 msgstr "Добавить"
 
 
@@ -193,11 +193,11 @@ msgstr "Добавить Location"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Добавить Сайт"
 msgstr "Добавить Сайт"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Добавить поток"
 msgstr "Добавить поток"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Добавлено успешно"
 msgstr "Добавлено успешно"
 
 
@@ -281,7 +281,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Вы уверены, что хотите удалить безвозвратно?"
 msgstr "Вы уверены, что хотите удалить безвозвратно?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Вы уверены, что хотите удалить?"
 msgstr "Вы уверены, что хотите удалить?"
 
 
@@ -582,6 +582,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Отмена"
 msgstr "Отмена"
 
 
@@ -1217,7 +1218,7 @@ msgstr "Определите имя и размер зоны общей памя
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Удалить"
 msgstr "Удалить"
 
 
@@ -1265,7 +1266,7 @@ msgstr "Не удалось удалить поток %{name} с %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Поток %{name} успешно удален с %{node}"
 msgstr "Поток %{name} успешно удален с %{node}"
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Удалить поток: %{stream_name}"
 msgstr "Удалить поток: %{stream_name}"
 
 
@@ -1339,13 +1340,10 @@ msgid "Directory path to store cache files"
 msgstr "Путь к каталогу для хранения кэшированных файлов"
 msgstr "Путь к каталогу для хранения кэшированных файлов"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Отключить"
 msgstr "Отключить"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Отключить"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
 msgstr "Не удалось отключить автоматическое продление для %{name}"
@@ -1406,15 +1404,16 @@ msgstr "Поток %{name} отключен от %{node} успешно"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Отключено"
 msgstr "Отключено"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Отключено успешно"
 msgstr "Отключено успешно"
 
 
@@ -1444,6 +1443,10 @@ msgstr "Не включайте эту опцию, если не уверены,
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "Вы хотите %{action} этот сайт?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Вы хотите отключить автоматическое обновление сертификата?"
 msgstr "Вы хотите отключить автоматическое обновление сертификата?"
@@ -1520,7 +1523,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Дублировать"
 msgstr "Дублировать"
 
 
@@ -1569,13 +1572,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 msgstr "Email (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "включить"
 msgstr "включить"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Включить"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Двухфакторная аутентификация успешно включена"
 msgstr "Двухфакторная аутентификация успешно включена"
@@ -1674,9 +1674,10 @@ msgstr "Включить TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Включено"
 msgstr "Включено"
@@ -1684,7 +1685,7 @@ msgstr "Включено"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Активировано успешно"
 msgstr "Активировано успешно"
 
 
@@ -1921,7 +1922,7 @@ msgstr "Не удалось удалить сертификат из базы д
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Не удалось отключить %{msg}"
 msgstr "Не удалось отключить %{msg}"
 
 
@@ -1931,7 +1932,7 @@ msgstr "Не удалось отключить режим обслуживани
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Не удалось включить %{msg}"
 msgstr "Не удалось включить %{msg}"
 
 
@@ -2674,7 +2675,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Техническое обслуживание"
 msgstr "Техническое обслуживание"
 
 
@@ -2703,7 +2704,7 @@ msgstr "Конфигурации"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Сайты"
 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"
 msgid "Manage Streams"
 msgstr "Управление потоками"
 msgstr "Управление потоками"
 
 
@@ -2877,12 +2878,12 @@ msgstr "Многострочная директива"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Имя"
 msgstr "Имя"
 
 
@@ -3145,7 +3146,7 @@ msgstr "Nginx.conf включает каталог streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Нет"
 msgstr "Нет"
 
 
@@ -3167,7 +3168,7 @@ msgid "Node"
 msgstr "Узел"
 msgstr "Узел"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Группа узлов"
 msgstr "Группа узлов"
@@ -3282,6 +3283,7 @@ msgstr "Выкл"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3299,7 +3301,8 @@ msgstr "Оффлайн"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "ОК"
 msgstr "ОК"
 
 
@@ -3708,6 +3711,15 @@ msgstr "Провайдер"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "Прокси"
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Номер в органах общественной безопасности"
 msgstr "Номер в органах общественной безопасности"
@@ -4573,7 +4585,7 @@ msgstr "Статический"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Статус"
 msgstr "Статус"
 
 
@@ -5154,6 +5166,7 @@ msgstr "Вторник"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Требуется двухфакторная аутентификация"
 msgstr "Требуется двухфакторная аутентификация"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5190,7 +5203,7 @@ msgstr "Успешно обновлено"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5224,6 +5237,10 @@ msgstr "Загрузить файлы"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Загрузить папки"
 msgstr "Загрузить папки"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Восходящий поток"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Имя Upstream"
 msgstr "Имя Upstream"
@@ -5516,6 +5533,12 @@ msgstr "Ваши старые коды больше не будут работа
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Ваши ключи доступа"
 msgstr "Ваши ключи доступа"
 
 
+#~ msgid "Disable"
+#~ msgstr "Отключить"
+
+#~ msgid "Enable"
+#~ msgstr "Включить"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Ошибка последнего резервного копирования"
 #~ msgstr "Ошибка последнего резервного копирования"
 
 
@@ -5703,9 +5726,6 @@ msgstr "Ваши ключи доступа"
 #~ "Синхронизация конфигурации %{config_name} с %{env_name} не удалась, ответ: "
 #~ "Синхронизация конфигурации %{config_name} с %{env_name} не удалась, ответ: "
 #~ "%{resp}"
 #~ "%{resp}"
 
 
-#~ msgid "Target"
-#~ msgstr "Цель"
-
 #~ msgid "File"
 #~ msgid "File"
 #~ msgstr "Файл"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "İşlemler"
 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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Ekle"
 msgstr "Ekle"
 
 
@@ -189,11 +189,11 @@ msgstr "Konum ekle"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Site Ekle"
 msgstr "Site Ekle"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Akış Ekle"
 msgstr "Akış Ekle"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Başarıyla eklendi"
 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?"
 msgstr "Kalıcı olarak silmek istediğinizden emin misiniz?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Silmek istediğine emin misin?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "İptal"
 msgstr "İptal"
 
 
@@ -1211,7 +1212,7 @@ msgstr ""
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Sil"
 msgstr "Sil"
 
 
@@ -1259,7 +1260,7 @@ msgstr "%{node} üzerindeki %{name} akışı silinemedi"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "%{name} akışı %{node} üzerinden başarıyla silindi"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Akışı sil: %{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"
 msgstr "Önbellek dosyalarını depolamak için dizin yolu"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Devre dışı bırak"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "%{name} için otomatik yenileme devre dışı bırakılamadı"
 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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Devre dışı"
 msgstr "Devre dışı"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Başarıyla devre dışı bırakıldı"
 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?"
 msgid "Do you want to %{action} this site?"
 msgstr "Bu siteyi %{action} etmek istiyor musunuz?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Otomatik sertifika yenilemeyi devre dışı bırakmak istiyor musunuz?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Kopyala"
 msgstr "Kopyala"
 
 
@@ -1565,13 +1568,10 @@ msgid "Email (*)"
 msgstr "E-posta(*)"
 msgstr "E-posta(*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "etkinleştir"
 msgstr "etkinleştir"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Etkinleştir"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA'yı başarıyla etkinleştirildi"
 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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Etkin"
 msgstr "Etkin"
@@ -1680,7 +1681,7 @@ msgstr "Etkin"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Başarıyla etkinleştirildi"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Devre dışı bırakılamadı %{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/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Etkinleştirilemedi %{msg}"
 msgstr "Etkinleştirilemedi %{msg}"
 
 
@@ -2673,7 +2674,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Bakım"
 msgstr "Bakım"
 
 
@@ -2702,7 +2703,7 @@ msgstr "Yapılandırmaları Yönet"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Siteleri Yönet"
 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"
 msgid "Manage Streams"
 msgstr "Akışları Yönet"
 msgstr "Akışları Yönet"
 
 
@@ -2876,12 +2877,12 @@ msgstr "Çok Satırlı Yönergeler"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "İsim"
 msgstr "İsim"
 
 
@@ -3144,7 +3145,7 @@ msgstr "Nginx.conf, streams-enabled dizinini içerir"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Hayır"
 msgstr "Hayır"
 
 
@@ -3166,7 +3167,7 @@ msgid "Node"
 msgstr "Düğüm"
 msgstr "Düğüm"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Düğüm Grubu"
 msgstr "Düğüm Grubu"
@@ -3281,6 +3282,7 @@ msgstr "Kapalı"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3298,7 +3300,8 @@ msgstr "Çevrimdışı"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "Tamam"
 msgstr "Tamam"
 
 
@@ -3702,6 +3705,15 @@ msgstr "Sağlayıcı"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Kamu Güvenlik Numarası"
 msgstr "Kamu Güvenlik Numarası"
@@ -4574,7 +4586,7 @@ msgstr "Statik"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Durum"
 msgstr "Durum"
 
 
@@ -5156,6 +5168,7 @@ msgstr "Salı"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 msgstr "İki faktörlü kimlik doğrulama gerekiyor"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: 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/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5226,6 +5239,10 @@ msgstr "Dosyaları Yükle"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Klasörleri Yükle"
 msgstr "Klasörleri Yükle"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Yukarı Akış"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Yukarı Akış Adı"
 msgstr "Yukarı Akış Adı"
@@ -5520,6 +5537,12 @@ msgstr "Eski kodlarınız artık çalışmayacak."
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Geçiş Anahtarlarınız"
 msgstr "Geçiş Anahtarlarınız"
 
 
+#~ msgid "Disable"
+#~ msgstr "Devre Dışı Bırak"
+
+#~ msgid "Enable"
+#~ msgstr "Etkinleştir"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Son Yedekleme Hatası"
 #~ 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 "
 #~ "Yapılandırma %{config_name} ile %{env_name} arasında eşitleme başarısız "
 #~ "oldu, yanıt: %{resp}"
 #~ "oldu, yanıt: %{resp}"
 
 
-#, fuzzy
-#~ msgid "Target"
-#~ msgstr "Hedef"
-
 #~ msgid "Can't scan? Use text key binding"
 #~ msgid "Can't scan? Use text key binding"
 #~ msgstr "Tarayamıyor musunuz? Metin anahtar bağlamasını kullanın"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Дії"
 msgstr "Дії"
@@ -166,7 +166,7 @@ msgstr "Фактичне співвідношення робочих до нал
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Додати"
 msgstr "Додати"
 
 
@@ -193,11 +193,11 @@ msgstr "Додати локацію"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Додати сайт"
 msgstr "Додати сайт"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Додати стрім"
 msgstr "Додати стрім"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Успішно додано"
 msgstr "Успішно додано"
 
 
@@ -281,7 +281,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "Ви впевнені, що хочете видалити назавжди?"
 msgstr "Ви впевнені, що хочете видалити назавжди?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "Ви впевнені, що хочете видалити?"
 msgstr "Ви впевнені, що хочете видалити?"
 
 
@@ -579,6 +579,7 @@ msgstr ""
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Скасувати"
 msgstr "Скасувати"
 
 
@@ -1247,7 +1248,7 @@ msgstr "Вкажіть назву та розмір зони спільної п
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Видалити"
 msgstr "Видалити"
 
 
@@ -1295,7 +1296,7 @@ msgstr "Не вдалося видалити потік %{name} з %{node}"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Потік %{name} успішно видалено з %{node}"
 msgstr "Потік %{name} успішно видалено з %{node}"
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Видалити потік: %{stream_name}"
 msgstr "Видалити потік: %{stream_name}"
 
 
@@ -1405,13 +1406,10 @@ msgid "Directory path to store cache files"
 msgstr "Шлях до каталогу для зберігання файлів кешу"
 msgstr "Шлях до каталогу для зберігання файлів кешу"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Вимкнути"
 msgstr "Вимкнути"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "Вимкнути"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Не вдалося вимкнути автоматичне поновлення для %{name}"
 msgstr "Не вдалося вимкнути автоматичне поновлення для %{name}"
@@ -1472,15 +1470,16 @@ msgstr "Потік %{name} успішно вимкнено з %{node}"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Вимкнено"
 msgstr "Вимкнено"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Успішно вимкнено"
 msgstr "Успішно вимкнено"
 
 
@@ -1510,6 +1509,10 @@ msgstr "Не вмикайте цю опцію, якщо ви не впевнен
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "Ви хочете %{action} цей сайт?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "Ви хочете вимкнути автоматичне оновлення сертифіката?"
 msgstr "Ви хочете вимкнути автоматичне оновлення сертифіката?"
@@ -1586,7 +1589,7 @@ msgstr ""
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Дублювати"
 msgstr "Дублювати"
 
 
@@ -1635,13 +1638,10 @@ msgid "Email (*)"
 msgstr "Електронна пошта (*)"
 msgstr "Електронна пошта (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "увімкнути"
 msgstr "увімкнути"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Увімкнути"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "2FA успішно ввімкнено"
 msgstr "2FA успішно ввімкнено"
@@ -1740,9 +1740,10 @@ msgstr "Увімкнути TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Увімкнено"
 msgstr "Увімкнено"
@@ -1750,7 +1751,7 @@ msgstr "Увімкнено"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Успішно ввімкнено"
 msgstr "Успішно ввімкнено"
 
 
@@ -1987,7 +1988,7 @@ msgstr "Не вдалося видалити сертифікат з бази д
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Не вдалося вимкнути %{msg}"
 msgstr "Не вдалося вимкнути %{msg}"
 
 
@@ -1997,7 +1998,7 @@ msgstr "Не вдалося вимкнути режим обслуговуван
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Не вдалося увімкнути %{msg}"
 msgstr "Не вдалося увімкнути %{msg}"
 
 
@@ -2740,7 +2741,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Технічне обслуговування"
 msgstr "Технічне обслуговування"
 
 
@@ -2769,7 +2770,7 @@ msgstr "Керування конфігураціями"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Керування сайтами"
 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"
 msgid "Manage Streams"
 msgstr "Керування потоками"
 msgstr "Керування потоками"
 
 
@@ -2943,12 +2944,12 @@ msgstr "Багаторядкова директива"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Ім'я"
 msgstr "Ім'я"
 
 
@@ -3211,7 +3212,7 @@ msgstr "Nginx.conf включає каталог streams-enabled"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Ні"
 msgstr "Ні"
 
 
@@ -3233,7 +3234,7 @@ msgid "Node"
 msgstr "Вузол"
 msgstr "Вузол"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Група вузлів"
 msgstr "Група вузлів"
@@ -3348,6 +3349,7 @@ msgstr "Вимкнено"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3365,7 +3367,8 @@ msgstr "Офлайн"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "Гаразд"
 msgstr "Гаразд"
 
 
@@ -3770,6 +3773,15 @@ msgstr "Провайдер"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "Проксі"
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Номер громадської безпеки"
 msgstr "Номер громадської безпеки"
@@ -4642,7 +4654,7 @@ msgstr "Статичний"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Статус"
 msgstr "Статус"
 
 
@@ -5222,6 +5234,7 @@ msgstr "Вівторок"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Потрібна двофакторна аутентифікація"
 msgstr "Потрібна двофакторна аутентифікація"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5258,7 +5271,7 @@ msgstr "Успішно оновлено"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5292,6 +5305,10 @@ msgstr "Завантажити файли"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Завантажити папки"
 msgstr "Завантажити папки"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "Вгору за течією"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Назва апстріму"
 msgstr "Назва апстріму"
@@ -5583,6 +5600,12 @@ msgstr "Ваші старі коди більше не працюватимут
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "Ваші ключі доступу"
 msgstr "Ваші ключі доступу"
 
 
+#~ msgid "Disable"
+#~ msgstr "Вимкнути"
+
+#~ msgid "Enable"
+#~ msgstr "Увімкнути"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "Помилка останнього резервного копіювання"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "Hành động"
 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/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "Thêm"
 msgstr "Thêm"
 
 
@@ -184,11 +184,11 @@ msgstr "Thêm Location"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "Thêm Website"
 msgstr "Thêm Website"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "Thêm luồng"
 msgstr "Thêm luồng"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "Đã thêm thành công"
 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?"
 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/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?"
 msgid "Are you sure you want to delete?"
 msgstr "Bạn có chắc chắn muốn xóa không?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "Huỷ"
 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/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "Xoá"
 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"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "Đã xóa luồng %{name} từ %{node} thành công"
 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}"
 msgid "Delete stream: %{stream_name}"
 msgstr "Xóa luồng: %{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"
 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/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "Vô hiệu hóa"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "Vô hiệu hóa gia hạn tự động thất bại cho %{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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "Đã tắt"
 msgstr "Đã tắt"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "Đã tắt thành công"
 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?"
 msgid "Do you want to %{action} this site?"
 msgstr "Bạn có muốn %{action} trang web này không?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 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 ?"
 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/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "Nhân bản"
 msgstr "Nhân bản"
 
 
@@ -1544,13 +1547,10 @@ msgid "Email (*)"
 msgstr "Email (*)"
 msgstr "Email (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "bật"
 msgstr "bật"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "Bật"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "Bật 2FA thành công"
 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/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "Đã bật"
 msgstr "Đã bật"
@@ -1659,7 +1660,7 @@ msgstr "Đã bật"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "Đã bật"
 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/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "Không thể tắt %{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/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "Không thể bật %{msg}"
 msgstr "Không thể bật %{msg}"
 
 
@@ -2648,7 +2649,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "Bảo trì"
 msgstr "Bảo trì"
 
 
@@ -2677,7 +2678,7 @@ msgstr "Quản lý cấu hình"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "Quản lý Website"
 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"
 msgid "Manage Streams"
 msgstr "Quản lý luồng"
 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/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "Tên"
 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/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "Không"
 msgstr "Không"
 
 
@@ -3141,7 +3142,7 @@ msgid "Node"
 msgstr "Nút"
 msgstr "Nút"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "Nhóm nút"
 msgstr "Nhóm nút"
@@ -3254,6 +3255,7 @@ msgstr "Tắt"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 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_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "Đồng ý"
 msgstr "Đồng ý"
 
 
@@ -3667,6 +3670,15 @@ msgstr "Nhà cung cấp"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "Số An ninh Công cộng"
 msgstr "Số An ninh Công cộng"
@@ -4530,7 +4542,7 @@ msgstr "Tĩnh"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "Trạng thái"
 msgstr "Trạng thái"
 
 
@@ -5110,6 +5122,7 @@ msgstr "Thứ Ba"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "Yêu cầu xác thực hai yếu tố"
 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/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: 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/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5180,6 +5193,10 @@ msgstr "Tải lên tệp"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "Tải lên thư mục"
 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
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Tên Upstream"
 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"
 msgid "Your passkeys"
 msgstr "Khóa truy cập của bạn"
 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"
 #~ msgid "Last Backup Error"
 #~ msgstr "Lỗi sao lưu cuối cùng"
 #~ 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}"
 #~ 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"
 #~ msgstr "Nhân bản %{conf_name} thành %{node_name} thành công"
 
 
-#~ msgid "Target"
-#~ msgstr "Mục tiêu"
-
 #~ msgid "File"
 #~ msgid "File"
 #~ msgstr "Tệp tin"
 #~ 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/nginx_log/NginxLogList.vue:52
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/notification/notificationColumns.tsx:72
 #: src/views/preference/components/ExternalNotify/columns.tsx:76
 #: 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
 #: src/views/user/userColumns.tsx:58
 msgid "Actions"
 msgid "Actions"
 msgstr "操作"
 msgstr "操作"
@@ -161,7 +161,7 @@ msgstr "实际工作进程与配置比例"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/components/NgxConfigEditor/NgxUpstream.vue:159 src/language/curd.ts:19
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/preference/tabs/CertSettings.vue:45
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
 #: src/views/site/site_edit/components/ConfigTemplate/ConfigTemplate.vue:94
-#: src/views/stream/StreamList.vue:121
+#: src/views/stream/StreamList.vue:101
 msgid "Add"
 msgid "Add"
 msgstr "添加"
 msgstr "添加"
 
 
@@ -188,11 +188,11 @@ msgstr "添加 Location"
 msgid "Add Site"
 msgid "Add Site"
 msgstr "添加站点"
 msgstr "添加站点"
 
 
-#: src/views/stream/StreamList.vue:174
+#: src/views/stream/StreamList.vue:138
 msgid "Add Stream"
 msgid "Add Stream"
 msgstr "添加 Stream"
 msgstr "添加 Stream"
 
 
-#: src/views/stream/StreamList.vue:92
+#: src/views/stream/StreamList.vue:72
 msgid "Added successfully"
 msgid "Added successfully"
 msgstr "添加成功"
 msgstr "添加成功"
 
 
@@ -276,7 +276,7 @@ msgid "Are you sure you want to delete permanently?"
 msgstr "确定要永久删除吗?"
 msgstr "确定要永久删除吗?"
 
 
 #: src/language/curd.ts:25 src/views/site/site_list/SiteList.vue:100
 #: 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?"
 msgid "Are you sure you want to delete?"
 msgstr "您确定要删除吗?"
 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/Cert/ObtainCert.vue:143
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:21
 #: src/views/stream/components/RightPanel/Basic.vue:47
 #: src/views/stream/components/RightPanel/Basic.vue:47
+#: src/views/stream/components/StreamStatusSelect.vue:65
 msgid "Cancel"
 msgid "Cancel"
 msgstr "取消"
 msgstr "取消"
 
 
@@ -1173,7 +1174,7 @@ msgstr "定义共享内存区名称和大小,例如 proxy_cache:10m"
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/components/NgxConfigEditor/NgxUpstream.vue:129 src/language/curd.ts:9
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/certificate/components/RemoveCert.vue:88
 #: src/views/site/site_list/SiteList.vue:109
 #: src/views/site/site_list/SiteList.vue:109
-#: src/views/stream/StreamList.vue:166
+#: src/views/stream/StreamList.vue:130
 msgid "Delete"
 msgid "Delete"
 msgstr "删除"
 msgstr "删除"
 
 
@@ -1221,7 +1222,7 @@ msgstr "从 %{node} 删除 Stream %{name} 失败"
 msgid "Delete stream %{name} from %{node} successfully"
 msgid "Delete stream %{name} from %{node} successfully"
 msgstr "已成功从 %{node} 删除 Stream %{name}"
 msgstr "已成功从 %{node} 删除 Stream %{name}"
 
 
-#: src/views/stream/StreamList.vue:67
+#: src/views/stream/StreamList.vue:47
 msgid "Delete stream: %{stream_name}"
 msgid "Delete stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 msgstr "删除 Stream: %{stream_name}"
 
 
@@ -1295,13 +1296,10 @@ msgid "Directory path to store cache files"
 msgstr "存储缓存文件的目录路径"
 msgstr "存储缓存文件的目录路径"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:115
 #: src/views/site/components/SiteStatusSelect.vue:115
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "disable"
 msgid "disable"
 msgstr "禁用"
 msgstr "禁用"
 
 
-#: src/views/stream/StreamList.vue:137
-msgid "Disable"
-msgstr "禁用"
-
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:80
 msgid "Disable auto-renewal failed for %{name}"
 msgid "Disable auto-renewal failed for %{name}"
 msgstr "禁用 %{name} 的自动续期失败"
 msgstr "禁用 %{name} 的自动续期失败"
@@ -1362,15 +1360,16 @@ msgstr "在 %{node} 上禁用 %{name} 成功"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/components/SiteStatusSelect.vue:162
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:62
 #: 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/StreamEditor.vue:30
+#: src/views/stream/components/StreamStatusSelect.vue:89
 #: src/views/user/userColumns.tsx:39
 #: src/views/user/userColumns.tsx:39
 msgid "Disabled"
 msgid "Disabled"
 msgstr "禁用"
 msgstr "禁用"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/site/components/SiteStatusSelect.vue:67
 #: src/views/stream/components/RightPanel/Basic.vue:34
 #: src/views/stream/components/RightPanel/Basic.vue:34
-#: src/views/stream/StreamList.vue:56
+#: src/views/stream/components/StreamStatusSelect.vue:39
 msgid "Disabled successfully"
 msgid "Disabled successfully"
 msgstr "禁用成功"
 msgstr "禁用成功"
 
 
@@ -1400,6 +1399,10 @@ msgstr "除非确定需要,否则不要启用该选项。"
 msgid "Do you want to %{action} this site?"
 msgid "Do you want to %{action} this site?"
 msgstr "您想将这个网站%{action}吗?"
 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
 #: src/views/site/site_edit/components/Cert/ObtainCert.vue:139
 msgid "Do you want to disable auto-cert renewal?"
 msgid "Do you want to disable auto-cert renewal?"
 msgstr "你想禁用自动更新证书吗?"
 msgstr "你想禁用自动更新证书吗?"
@@ -1473,7 +1476,7 @@ msgstr "由于某些浏览器的安全策略,除非在 localhost 上使用,
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteDuplicate.vue:72
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/site/site_list/SiteList.vue:95
 #: src/views/stream/components/StreamDuplicate.vue:64
 #: src/views/stream/components/StreamDuplicate.vue:64
-#: src/views/stream/StreamList.vue:152
+#: src/views/stream/StreamList.vue:116
 msgid "Duplicate"
 msgid "Duplicate"
 msgstr "复制"
 msgstr "复制"
 
 
@@ -1522,13 +1525,10 @@ msgid "Email (*)"
 msgstr "邮箱 (*)"
 msgstr "邮箱 (*)"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:114
 #: src/views/site/components/SiteStatusSelect.vue:114
+#: src/views/stream/components/StreamStatusSelect.vue:58
 msgid "enable"
 msgid "enable"
 msgstr "启用"
 msgstr "启用"
 
 
-#: src/views/stream/StreamList.vue:145
-msgid "Enable"
-msgstr "启用"
-
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 #: src/views/preference/components/AuthSettings/TOTP.vue:45
 msgid "Enable 2FA successfully"
 msgid "Enable 2FA successfully"
 msgstr "二步验证启用成功"
 msgstr "二步验证启用成功"
@@ -1627,9 +1627,10 @@ msgstr "启用 TOTP"
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/preference/tabs/NodeSettings.vue:30
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/components/SiteStatusSelect.vue:159
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:56
 #: 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/RightPanel/Basic.vue:62
 #: src/views/stream/components/StreamEditor.vue:24
 #: src/views/stream/components/StreamEditor.vue:24
+#: src/views/stream/components/StreamStatusSelect.vue:88
 #: src/views/user/userColumns.tsx:36
 #: src/views/user/userColumns.tsx:36
 msgid "Enabled"
 msgid "Enabled"
 msgstr "启用"
 msgstr "启用"
@@ -1637,7 +1638,7 @@ msgstr "启用"
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/components/SiteStatusSelect.vue:54
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/site/site_add/SiteAdd.vue:32
 #: src/views/stream/components/RightPanel/Basic.vue:25
 #: src/views/stream/components/RightPanel/Basic.vue:25
-#: src/views/stream/StreamList.vue:46
+#: src/views/stream/components/StreamStatusSelect.vue:26
 msgid "Enabled successfully"
 msgid "Enabled successfully"
 msgstr "启用成功"
 msgstr "启用成功"
 
 
@@ -1874,7 +1875,7 @@ msgstr "从数据库中删除证书失败:%{error}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/site/components/SiteStatusSelect.vue:73
 #: src/views/stream/components/RightPanel/Basic.vue:37
 #: 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}"
 msgid "Failed to disable %{msg}"
 msgstr "禁用失败 %{msg}"
 msgstr "禁用失败 %{msg}"
 
 
@@ -1884,7 +1885,7 @@ msgstr "停用维护模式失败 %{msg}"
 
 
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/site/components/SiteStatusSelect.vue:60
 #: src/views/stream/components/RightPanel/Basic.vue:28
 #: 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}"
 msgid "Failed to enable %{msg}"
 msgstr "启用失败 %{msg}"
 msgstr "启用失败 %{msg}"
 
 
@@ -2609,7 +2610,7 @@ msgstr ""
 
 
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/components/SiteStatusSelect.vue:165
 #: src/views/site/site_edit/components/SiteEditor/SiteEditor.vue:68
 #: 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"
 msgid "Maintenance"
 msgstr "维护模式"
 msgstr "维护模式"
 
 
@@ -2636,7 +2637,7 @@ msgstr "配置管理"
 msgid "Manage Sites"
 msgid "Manage Sites"
 msgstr "网站管理"
 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"
 msgid "Manage Streams"
 msgstr "管理 Stream"
 msgstr "管理 Stream"
 
 
@@ -2810,12 +2811,12 @@ msgstr "多行指令"
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/nginx_log/NginxLogList.vue:36
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/preference/components/AuthSettings/AddPasskey.vue:75
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:35
 #: 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/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/RightPanel/Basic.vue:69
 #: src/views/stream/components/StreamDuplicate.vue:71
 #: src/views/stream/components/StreamDuplicate.vue:71
-#: src/views/stream/StreamList.vue:179
+#: src/views/stream/StreamList.vue:143
 msgid "Name"
 msgid "Name"
 msgstr "名称"
 msgstr "名称"
 
 
@@ -3076,7 +3077,7 @@ msgstr "检查 nginx.conf 是否包含 streams-enabled 的目录"
 #: src/views/notification/Notification.vue:38
 #: src/views/notification/Notification.vue:38
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/AuthSettings.vue:132
 #: src/views/preference/tabs/CertSettings.vue:73
 #: 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"
 msgid "No"
 msgstr "取消"
 msgstr "取消"
 
 
@@ -3098,7 +3099,7 @@ msgid "Node"
 msgstr "节点"
 msgstr "节点"
 
 
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:41
 #: 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
 #: src/views/stream/components/RightPanel/Basic.vue:77
 msgid "Node Group"
 msgid "Node Group"
 msgstr "节点组"
 msgstr "节点组"
@@ -3207,6 +3208,7 @@ msgstr "关闭"
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/EnvGroupTabs/EnvGroupTabs.vue:159
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NgxConfigEditor/NgxUpstream.vue:145
 #: src/components/NodeSelector/NodeSelector.vue:109
 #: src/components/NodeSelector/NodeSelector.vue:109
+#: src/components/ProxyTargets/ProxyTargets.vue:43
 #: src/views/dashboard/Environments.vue:107
 #: src/views/dashboard/Environments.vue:107
 #: src/views/environments/list/envColumns.tsx:55
 #: src/views/environments/list/envColumns.tsx:55
 msgid "Offline"
 msgid "Offline"
@@ -3224,7 +3226,8 @@ msgstr "离线"
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_edit/components/EnableTLS/EnableTLS.vue:20
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/site/site_list/SiteList.vue:99
 #: src/views/stream/components/RightPanel/Basic.vue:46
 #: 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"
 msgid "OK"
 msgstr "确定"
 msgstr "确定"
 
 
@@ -3608,6 +3611,15 @@ msgstr "提供商"
 msgid "Proxy"
 msgid "Proxy"
 msgstr "代理"
 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
 #: src/views/preference/tabs/NodeSettings.vue:46
 msgid "Public Security Number"
 msgid "Public Security Number"
 msgstr "公安备案号"
 msgstr "公安备案号"
@@ -4460,7 +4472,7 @@ msgstr "静态"
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/dashboard/components/ModulesTable.vue:96
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/environments/list/envColumns.tsx:43
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:28
 #: 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"
 msgid "Status"
 msgstr "状态"
 msgstr "状态"
 
 
@@ -4989,6 +5001,7 @@ msgstr "星期二"
 msgid "Two-factor authentication required"
 msgid "Two-factor authentication required"
 msgstr "需要两步验证"
 msgstr "需要两步验证"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/certificate/CertificateList/certColumns.tsx:24
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/dashboard/components/ModulesTable.vue:83
 #: src/views/nginx_log/NginxLogList.vue:12
 #: src/views/nginx_log/NginxLogList.vue:12
@@ -5025,7 +5038,7 @@ msgstr "更新成功"
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/group/columns.ts:35
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/environments/list/envColumns.tsx:89
 #: src/views/site/site_edit/components/RightPanel/Basic.vue:38
 #: 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/stream/components/RightPanel/Basic.vue:73
 #: src/views/user/userColumns.tsx:52
 #: src/views/user/userColumns.tsx:52
 msgid "Updated at"
 msgid "Updated at"
@@ -5059,6 +5072,10 @@ msgstr "上传文件"
 msgid "Upload Folders"
 msgid "Upload Folders"
 msgstr "上传文件夹"
 msgstr "上传文件夹"
 
 
+#: src/components/ProxyTargets/ProxyTargets.vue:48
+msgid "Upstream"
+msgstr "上游"
+
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 #: src/components/NgxConfigEditor/NgxUpstream.vue:177
 msgid "Upstream Name"
 msgid "Upstream Name"
 msgstr "Upstream 名称"
 msgstr "Upstream 名称"
@@ -5329,6 +5346,12 @@ msgstr "您的旧代码将不再有效。"
 msgid "Your passkeys"
 msgid "Your passkeys"
 msgstr "你的 Passkeys"
 msgstr "你的 Passkeys"
 
 
+#~ msgid "Disable"
+#~ msgstr "禁用"
+
+#~ msgid "Enable"
+#~ msgstr "启用"
+
 #~ msgid "Last Backup Error"
 #~ msgid "Last Backup Error"
 #~ msgstr "最后一次备份错误"
 #~ msgstr "最后一次备份错误"
 
 
@@ -5546,9 +5569,6 @@ msgstr "你的 Passkeys"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgid "Sync config %{config_name} to %{env_name} failed, response: %{resp}"
 #~ msgstr "同步配置 %{config_name} 到 %{env_name} 失败,响应:%{resp}"
 #~ msgstr "同步配置 %{config_name} 到 %{env_name} 失败,响应:%{resp}"
 
 
-#~ msgid "Target"
-#~ msgstr "目标"
-
 #~ msgid ""
 #~ msgid ""
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "If you lose your mobile phone, you can use the recovery code to reset your "
 #~ "2FA."
 #~ "2FA."

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

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

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

@@ -1,9 +1,11 @@
 import { useGlobalStore } from './moudule/global'
 import { useGlobalStore } from './moudule/global'
+import { useProxyAvailabilityStore } from './moudule/proxyAvailability'
 import { useSettingsStore } from './moudule/settings'
 import { useSettingsStore } from './moudule/settings'
 import { useUserStore } from './moudule/user'
 import { useUserStore } from './moudule/user'
 
 
 export {
 export {
   useGlobalStore,
   useGlobalStore,
+  useProxyAvailabilityStore,
   useSettingsStore,
   useSettingsStore,
   useUserStore,
   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 { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
 import { Tag } from 'ant-design-vue'
 import { Tag } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import env_group from '@/api/env_group'
+import ProxyTargets from '@/components/ProxyTargets'
 import { ConfigStatus } from '@/constants'
 import { ConfigStatus } from '@/constants'
 import envGroupColumns from '@/views/environments/group/columns'
 import envGroupColumns from '@/views/environments/group/columns'
 import SiteStatusSelect from '@/views/site/components/SiteStatusSelect.vue'
 import SiteStatusSelect from '@/views/site/components/SiteStatusSelect.vue'
@@ -53,12 +54,24 @@ const columns: StdTableColumn[] = [{
       }
       }
 
 
       template.push(
       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)
     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'),
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
   dataIndex: 'env_group_id',
@@ -123,7 +136,7 @@ const columns: StdTableColumn[] = [{
   },
   },
   sorter: true,
   sorter: true,
   pure: true,
   pure: true,
-  width: 50,
+  width: 100,
   fixed: 'right',
   fixed: 'right',
 }, {
 }, {
   title: () => $gettext('Actions'),
   title: () => $gettext('Actions'),

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

@@ -41,26 +41,6 @@ watch(route, () => {
   inspect_config.value?.test()
   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) {
 function destroy(stream_name: string) {
   stream.deleteItem(stream_name).then(() => {
   stream.deleteItem(stream_name).then(() => {
     curd.value.refresh()
     curd.value.refresh()
@@ -128,22 +108,6 @@ function handleAddStream() {
       </template>
       </template>
 
 
       <template #afterActions="{ record }">
       <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
         <AButton
           type="link"
           type="link"
           size="small"
           size="small"

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

@@ -1,10 +1,12 @@
 import type { CustomRenderArgs, StdTableColumn } from '@uozi-admin/curd'
 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 type { JSXElements } from '@/types'
 import { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
 import { actualFieldRender, datetimeRender } from '@uozi-admin/curd'
-import { Badge } from 'ant-design-vue'
 import env_group from '@/api/env_group'
 import env_group from '@/api/env_group'
-import { ConfigStatus } from '@/constants'
+import ProxyTargets from '@/components/ProxyTargets'
 import envGroupColumns from '@/views/environments/group/columns'
 import envGroupColumns from '@/views/environments/group/columns'
+import StreamStatusSelect from '@/views/stream/components/StreamStatusSelect.vue'
 
 
 const columns: StdTableColumn[] = [{
 const columns: StdTableColumn[] = [{
   title: () => $gettext('Name'),
   title: () => $gettext('Name'),
@@ -16,6 +18,28 @@ const columns: StdTableColumn[] = [{
   },
   },
   search: true,
   search: true,
   width: 150,
   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'),
   title: () => $gettext('Node Group'),
   dataIndex: 'env_group_id',
   dataIndex: 'env_group_id',
@@ -30,40 +54,57 @@ const columns: StdTableColumn[] = [{
       selectionType: 'radio',
       selectionType: 'radio',
     },
     },
   },
   },
+  batchEdit: true,
+  sorter: true,
+  pure: true,
+  width: 100,
+}, {
+  title: () => $gettext('Updated at'),
+  dataIndex: 'modified_at',
+  customRender: datetimeRender,
   sorter: true,
   sorter: true,
   pure: true,
   pure: true,
   width: 150,
   width: 150,
 }, {
 }, {
   title: () => $gettext('Status'),
   title: () => $gettext('Status'),
   dataIndex: '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,
   sorter: true,
   pure: 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'),
   title: () => $gettext('Actions'),
   dataIndex: 'actions',
   dataIndex: 'actions',
-  width: 250,
+  width: 80,
   fixed: 'right',
   fixed: 'right',
 }]
 }]
 
 

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

@@ -1,5 +1,5 @@
 <script setup lang="ts">
 <script setup lang="ts">
-import type { CheckedType } from '@/types'
+import type { SiteStatus } from '@/api/site'
 import { InfoCircleOutlined } from '@ant-design/icons-vue'
 import { InfoCircleOutlined } from '@ant-design/icons-vue'
 import { StdSelector } from '@uozi-admin/curd'
 import { StdSelector } from '@uozi-admin/curd'
 import { message, Modal } from 'ant-design-vue'
 import { message, Modal } from 'ant-design-vue'
@@ -7,15 +7,17 @@ import { storeToRefs } from 'pinia'
 import envGroup from '@/api/env_group'
 import envGroup from '@/api/env_group'
 import stream from '@/api/stream'
 import stream from '@/api/stream'
 import NodeSelector from '@/components/NodeSelector'
 import NodeSelector from '@/components/NodeSelector'
+import { ConfigStatus } from '@/constants'
 import { formatDateTime } from '@/lib/helper'
 import { formatDateTime } from '@/lib/helper'
 import { useSettingsStore } from '@/pinia'
 import { useSettingsStore } from '@/pinia'
 import envGroupColumns from '@/views/environments/group/columns'
 import envGroupColumns from '@/views/environments/group/columns'
 import { useStreamEditorStore } from '../../store'
 import { useStreamEditorStore } from '../../store'
 import ConfigName from '../ConfigName.vue'
 import ConfigName from '../ConfigName.vue'
+import StreamStatusSelect from '../StreamStatusSelect.vue'
 
 
 const settings = useSettingsStore()
 const settings = useSettingsStore()
 const store = useStreamEditorStore()
 const store = useStreamEditorStore()
-const { name, enabled, data } = storeToRefs(store)
+const { name, status, data } = storeToRefs(store)
 
 
 const [modal, ContextHolder] = Modal.useModal()
 const [modal, ContextHolder] = Modal.useModal()
 const showSync = computed(() => !settings.is_remote)
 const showSync = computed(() => !settings.is_remote)
@@ -23,7 +25,7 @@ const showSync = computed(() => !settings.is_remote)
 function enable() {
 function enable() {
   stream.enable(name.value).then(() => {
   stream.enable(name.value).then(() => {
     message.success($gettext('Enabled successfully'))
     message.success($gettext('Enabled successfully'))
-    enabled.value = true
+    status.value = ConfigStatus.Enabled
   }).catch(r => {
   }).catch(r => {
     message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
     message.error($gettext('Failed to enable %{msg}', { msg: r.message ?? '' }), 10)
   })
   })
@@ -32,21 +34,21 @@ function enable() {
 function disable() {
 function disable() {
   stream.disable(name.value).then(() => {
   stream.disable(name.value).then(() => {
     message.success($gettext('Disabled successfully'))
     message.success($gettext('Disabled successfully'))
-    enabled.value = false
+    status.value = ConfigStatus.Disabled
   }).catch(r => {
   }).catch(r => {
     message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
     message.error($gettext('Failed to disable %{msg}', { msg: r.message ?? '' }))
   })
   })
 }
 }
 
 
-function onChangeEnabled(checked: CheckedType) {
+function onChangeEnabled({ status }: { status: SiteStatus }) {
   modal.confirm({
   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,
     mask: false,
     centered: true,
     centered: true,
     okText: $gettext('OK'),
     okText: $gettext('OK'),
     cancelText: $gettext('Cancel'),
     cancelText: $gettext('Cancel'),
     async onOk() {
     async onOk() {
-      if (checked)
+      if (status === ConfigStatus.Enabled)
         enable()
         enable()
       else
       else
         disable()
         disable()
@@ -60,9 +62,10 @@ function onChangeEnabled(checked: CheckedType) {
     <ContextHolder />
     <ContextHolder />
 
 
     <AFormItem :label="$gettext('Enabled')">
     <AFormItem :label="$gettext('Enabled')">
-      <ASwitch
-        :checked="enabled"
-        @change="onChangeEnabled"
+      <StreamStatusSelect
+        v-model:status="status"
+        :stream-name="name"
+        @status-changed="onChangeEnabled"
       />
       />
     </AFormItem>
     </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 ConfigHistory from '@/components/ConfigHistory'
 import FooterToolBar from '@/components/FooterToolbar'
 import FooterToolBar from '@/components/FooterToolbar'
 import NgxConfigEditor from '@/components/NgxConfigEditor'
 import NgxConfigEditor from '@/components/NgxConfigEditor'
+import { ConfigStatus } from '@/constants'
 import { useStreamEditorStore } from '../store'
 import { useStreamEditorStore } from '../store'
 
 
 const router = useRouter()
 const router = useRouter()
 
 
 const store = useStreamEditorStore()
 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)
 const showHistory = ref(false)
 </script>
 </script>
 
 
@@ -18,7 +19,7 @@ const showHistory = ref(false)
     <template #title>
     <template #title>
       <span style="margin-right: 10px">{{ $gettext('Edit %{n}', { n: name }) }}</span>
       <span style="margin-right: 10px">{{ $gettext('Edit %{n}', { n: name }) }}</span>
       <ATag
       <ATag
-        v-if="enabled"
+        v-if="status === ConfigStatus.Enabled"
         color="blue"
         color="blue"
       >
       >
         {{ $gettext('Enabled') }}
         {{ $gettext('Enabled') }}
@@ -88,7 +89,7 @@ const showHistory = ref(false)
         class="domain-edit-container"
         class="domain-edit-container"
       >
       >
         <NgxConfigEditor
         <NgxConfigEditor
-          :enabled="enabled"
+          :enabled="status === ConfigStatus.Enabled"
           context="stream"
           context="stream"
         />
         />
       </div>
       </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 ngx from '@/api/ngx'
 import stream from '@/api/stream'
 import stream from '@/api/stream'
 import { useNgxConfigStore } from '@/components/NgxConfigEditor'
 import { useNgxConfigStore } from '@/components/NgxConfigEditor'
+import { ConfigStatus } from '@/constants'
 
 
 export const useStreamEditorStore = defineStore('streamEditor', () => {
 export const useStreamEditorStore = defineStore('streamEditor', () => {
   const name = ref('')
   const name = ref('')
@@ -20,7 +21,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
   const certInfoMap = ref({}) as Ref<Record<number, CertificateInfo[]>>
   const certInfoMap = ref({}) as Ref<Record<number, CertificateInfo[]>>
   const filename = ref('')
   const filename = ref('')
   const filepath = ref('')
   const filepath = ref('')
-  const enabled = ref(false)
+  const status = ref(ConfigStatus.Disabled)
 
 
   const ngxConfigStore = useNgxConfigStore()
   const ngxConfigStore = useNgxConfigStore()
   const { ngxConfig, configText, curServerIdx, curServer, curServerDirectives, curDirectivesMap } = storeToRefs(ngxConfigStore)
   const { ngxConfig, configText, curServerIdx, curServer, curServerDirectives, curDirectivesMap } = storeToRefs(ngxConfigStore)
@@ -90,7 +91,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
     if (r.advanced)
     if (r.advanced)
       advanceMode.value = true
       advanceMode.value = true
 
 
-    enabled.value = r.enabled
+    status.value = r.status
     parseErrorStatus.value = false
     parseErrorStatus.value = false
     parseErrorMessage.value = ''
     parseErrorMessage.value = ''
     filename.value = r.name
     filename.value = r.name
@@ -152,7 +153,7 @@ export const useStreamEditorStore = defineStore('streamEditor', () => {
     filename,
     filename,
     filepath,
     filepath,
     configText,
     configText,
-    enabled,
+    status,
     init,
     init,
     save,
     save,
     handleModeChange,
     handleModeChange,

+ 7 - 7
internal/cache/index.go

@@ -160,8 +160,8 @@ func (s *Scanner) Initialize(ctx context.Context) error {
 	configDir := filepath.Dir(nginx.GetConfPath())
 	configDir := filepath.Dir(nginx.GetConfPath())
 	availableDir := nginx.GetConfPath("sites-available")
 	availableDir := nginx.GetConfPath("sites-available")
 	enabledDir := nginx.GetConfPath("sites-enabled")
 	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
 	// Watch the main directories
 	err = s.watcher.Add(configDir)
 	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 {
 	if _, err := os.Stat(streamAvailableDir); err == nil {
 		err = s.watcher.Add(streamAvailableDir)
 		err = s.watcher.Add(streamAvailableDir)
 		if err != nil {
 		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 {
 	if _, err := os.Stat(streamEnabledDir); err == nil {
 		err = s.watcher.Add(streamEnabledDir)
 		err = s.watcher.Add(streamEnabledDir)
 		if err != nil {
 		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)
 	streamAvailableFiles, err := os.ReadDir(streamAvailablePath)
 	if err == nil {
 	if err == nil {
 		for _, file := range streamAvailableFiles {
 		for _, file := range streamAvailableFiles {

+ 5 - 0
internal/config/config.go

@@ -3,6 +3,7 @@ package config
 import (
 import (
 	"time"
 	"time"
 
 
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
 	"github.com/sashabaranov/go-openai"
 )
 )
@@ -15,6 +16,9 @@ const (
 	StatusMaintenance ConfigStatus = "maintenance"
 	StatusMaintenance ConfigStatus = "maintenance"
 )
 )
 
 
+// ProxyTarget is an alias for upstream.ProxyTarget
+type ProxyTarget = upstream.ProxyTarget
+
 type Config struct {
 type Config struct {
 	Name            string                         `json:"name"`
 	Name            string                         `json:"name"`
 	Content         string                         `json:"content"`
 	Content         string                         `json:"content"`
@@ -28,6 +32,7 @@ type Config struct {
 	Status          ConfigStatus                   `json:"status"`
 	Status          ConfigStatus                   `json:"status"`
 	Dir             string                         `json:"dir"`
 	Dir             string                         `json:"dir"`
 	Urls            []string                       `json:"urls,omitempty"`
 	Urls            []string                       `json:"urls,omitempty"`
+	ProxyTargets    []ProxyTarget                  `json:"proxy_targets,omitempty"`
 	SyncNodeIds     []uint64                       `json:"sync_node_ids,omitempty"`
 	SyncNodeIds     []uint64                       `json:"sync_node_ids,omitempty"`
 	SyncOverwrite   bool                           `json:"sync_overwrite"`
 	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"
 	"strings"
 
 
 	"github.com/0xJacky/Nginx-UI/internal/cache"
 	"github.com/0xJacky/Nginx-UI/internal/cache"
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 )
 )
 
 
 type SiteIndex struct {
 type SiteIndex struct {
-	Path    string
-	Content string
-	Urls    []string
+	Path         string
+	Content      string
+	Urls         []string
+	ProxyTargets []ProxyTarget
 }
 }
 
 
 var (
 var (
@@ -41,9 +43,10 @@ func scanForSite(configPath string, content []byte) error {
 	serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
 	serverBlocks := serverBlockRegex.FindAllSubmatch(content, -1)
 
 
 	siteIndex := SiteIndex{
 	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
 	// 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)
 		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
 		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/cert"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/0xJacky/Nginx-UI/internal/upstream"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/sashabaranov/go-openai"
 	"github.com/sashabaranov/go-openai"
 )
 )
@@ -17,6 +18,9 @@ const (
 	SiteStatusMaintenance SiteStatus = "maintenance"
 	SiteStatusMaintenance SiteStatus = "maintenance"
 )
 )
 
 
+// ProxyTarget is an alias for upstream.ProxyTarget
+type ProxyTarget = upstream.ProxyTarget
+
 type Site struct {
 type Site struct {
 	*model.Site
 	*model.Site
 	Name            string                         `json:"name"`
 	Name            string                         `json:"name"`
@@ -28,4 +32,5 @@ type Site struct {
 	Tokenized       *nginx.NgxConfig               `json:"tokenized,omitempty"`
 	Tokenized       *nginx.NgxConfig               `json:"tokenized,omitempty"`
 	CertInfo        map[int][]*cert.Info           `json:"cert_info,omitempty"`
 	CertInfo        map[int][]*cert.Info           `json:"cert_info,omitempty"`
 	Filepath        string                         `json:"filepath"`
 	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)
+		}
+	}
+}